Compare commits

..

No commits in common. "main" and "v0.10.3" have entirely different histories.

2425 changed files with 15125 additions and 175367 deletions

View file

@ -1,10 +0,0 @@
# Ignored for `Dockerfile` build tinymist cli
target
node_modules
editors
tools
refs
local
syntaxes
docs
contrib

1
.gitattributes vendored
View file

@ -1 +0,0 @@
.zed/settings.json linguist-language=json5

59
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,59 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior (Library test):
1. Declare a rust test 'fn test_xxx() { ... }' or typescript test 'it_should(function () { ... })'
2. Execute test function
3. See error
Or (Shell code):
1. Attach necessary resources to execute shell code
2. Put down some shell code 'cargo run --bin typst-ts-cli -- ...'
3. Execute shell code
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Package/Software version:**
VSCode version(Help -> About):
```plain
Version: 1.81.1
Commit: 6c3e3dba23e8fadc360aed75ce363ba185c49794
Date: 2023-08-09T22:18:39.991Z
Electron: 22.3.18
ElectronBuildId: 22689846
Chromium: 108.0.5359.215
Node.js: 16.17.1
V8: 10.8.168.25-electron.0
OS: Linux x64 6.4.12-x64v4-xanmod1
```
tinymist extension version: `v0.7.3`
**Logs:**
tinymist server log(Output Panel -> tinymist):
```plain
```
tinymist client log(Help -> Toggle Developer Tools -> Console):
```plain
```
**Additional context**
Add any other context about the problem here.

View file

@ -1,92 +0,0 @@
name: Bug report
description: File a bug/issue
labels: ["bug", "need-to-investigate"]
body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the bug you encountered.
options:
- label: I have searched the existing issues
required: true
- type: dropdown
attributes:
label: Platform
description: Which platform are you on?
options:
- x64 Windows (win32-x64, Most Common)
- x64 Linux (linux-x64, Most Common)
- Apple Silicon macOS (darwin-arm64, Most Common)
- ARM64 Windows (win32-arm64)
- ARM64 Linux (linux-arm64)
- ARMv7 Linux (linux-armhf)
- Intel macOS (darwin-x64)
- x64 Alpine Linux (alpine-x64)
- ARM64 Alpine Linux (alpine-arm64)
- Browser (web)
- Other Platforms (universal)
validations:
required: true
- type: dropdown
attributes:
label: Editor
description: Which editor are you using?
options:
- VS Cod(e,ium)
- Neovim
- Emacs
- Sublime Text
- Helix
- Zed
- CLI (Command Line Interface)
- Other
validations:
required: true
- type: textarea
attributes:
label: Editor Version
description: |
For example, in VSCode, get the version in (Help -> About)
validations:
required: true
- type: textarea
attributes:
label: Describe the bug
description: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
attributes:
label: Server Logs
description: |
For example, in Neovim, the log is oftenly stored in the `~/.local/state/nvim/lsp`.
For example, in VSCode, get the logs in (Output Panel -> Tinymist).
We may close a bug report if there is no full logs. Please don't truncate it for us, attach it as a file if it's too long.
value: |
```log
Paste your logs here
```
validations:
required: true
- type: textarea
attributes:
label: Browser Logs
description: |
For example, in VSCode, get the logs in (Help -> Toggle Developer Tools -> Console).
If you open preview in browser, the console log in the browser is also helpful.
We may close a bug report if there is no full logs. Please don't truncate it for us, attach it as a file if it's too long.
value: |
```log
Paste your logs here
```
validations:
required: true
- type: textarea
attributes:
label: Additional context
description: |
Links? References? Anything that will give us more context about the issue you are encountering!
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
required: false

View file

@ -1,26 +0,0 @@
name: Feature request
description: Create a feature request
labels: ["enhancement", "need-to-investigate"]
body:
- type: textarea
attributes:
label: Motivation
description: |
A brief use case, scenario, or other argument used in support of this *feature*.
validations:
required: false
- type: textarea
attributes:
label: Description
description: |
A clear and concise description of this *feature*.
validations:
required: false
- type: textarea
attributes:
label: More Examples/Questions
description: |
- Using examples to profile how you imagine the *feature* should be
- Raising questions to highlight the uncertain design for the *feature*.
validations:
required: false

View file

@ -1,337 +0,0 @@
# Localization Instructions for Claude/Copilot
This document provides comprehensive guidance for adding, updating, and maintaining localization (l10n) in the Tinymist project.
## Overview
Tinymist's localization system supports multiple languages across two main components:
- **Rust backend** (`crates/`): Language server functionality
- **VSCode extension** (`editors/vscode/`): Editor integration
The localization process has two main phases:
1. **Marking and Extracting Messages**: Adding localization calls in code and extracting them
2. **Translating Messages**: Adding translations to locale files
## Phase 1: Marking and Extracting Messages
### Rust Backend (`crates/`)
#### Adding Localized Messages
Use the `tinymist_l10n::t!` macro with a key and default English message:
```rust
// Simple message
let message = tinymist_l10n::t!("error.file-not-found", "File not found");
// Message with parameters
let message = tinymist_l10n::t!(
"error.invalid-config",
"Invalid configuration: {key}",
key = config_key
);
```
**Note**: Use the full `tinymist_l10n::t!` path - no need to import the macro separately.
#### Key Naming Convention
Use hierarchical dot-separated keys:
- `component.category.specific-action`
- Examples:
- `tinymist-query.code-action.exportPdf`
- `tinymist.config.badServerConfig`
- `tinymist-project.validate-error.root-path-not-absolute`
### TypeScript/VSCode Extension (`editors/vscode/`)
#### Adding Localized Messages
Use the `l10nMsg` function imported from `../l10n`:
```typescript
import { l10nMsg } from "../l10n";
// Simple message
const message = l10nMsg("Export as PDF");
// Message with parameters
const message = l10nMsg("Processing {count} files", { count: fileCount });
```
#### Import Pattern
Always import `l10nMsg` from the relative path to `l10n.ts`:
```typescript
import { l10nMsg } from "../l10n"; // Adjust path as needed
```
### Extracting Messages
After adding localized messages to code, extract them using:
```bash
yarn build:l10n
```
This command:
1. Scans Rust and TypeScript files for localization calls
2. Extracts messages to TOML files in `locales/`:
- `locales/tinymist-rt.toml` (Rust messages)
- `locales/tinymist-vscode-rt.toml` (TypeScript messages)
3. Updates existing translations while preserving manual edits
#### When to Run Extraction
- **Required**: After adding new localized messages to code
- **Not needed**: When only editing translations in `locales/` files
## Phase 2: Translating Messages
### Locale File Format
Locale files use TOML format designed for easy modification by LLMs:
```toml
# The translations are partially generated by copilot
[key.name]
en = "English message"
zh = "Chinese translation"
zh-TW = "Traditional Chinese translation"
fr = "French translation"
```
### Available Locale Files
- `locales/tinymist-vscode.toml` - VSCode extension UI (manual translations)
- `locales/tinymist-vscode-rt.toml` - VSCode extension runtime (auto-extracted)
- `locales/tinymist-rt.toml` - Rust backend runtime (auto-extracted)
### Adding Translations
#### Example: Adding a French Translation
```toml
[extension.tinymist.command.tinymist.exportCurrentPdf]
en = "Export the Opened File as PDF"
zh = "将当前打开的文件导出为 PDF"
fr = "Exporter le fichier ouvert en PDF" # Add this line
```
#### Example: Adding Support for a New Language
```toml
[extension.tinymist.command.tinymist.restartServer]
en = "Restart server"
zh = "重启服务器"
es = "Reiniciar servidor" # New Spanish translation
de = "Server neu starten" # New German translation
```
### Translation Guidelines
1. **Preserve Parameters**: Keep parameter placeholders like `{key}`, `{count}`, `{value}`
2. **Maintain Formatting**: Preserve newlines and special characters
3. **Context Awareness**: Consider the UI context where the message appears
4. **Consistency**: Use consistent terminology across related messages
### Language Codes
Use standard language codes:
- `en` - English (required, default)
- `zh` - Simplified Chinese
- `zh-TW` - Traditional Chinese
- `fr` - French
- `de` - German
- `es` - Spanish
- `ja` - Japanese
- `ru` - Russian
## Examples
### Example 1: Adding a New Error Message (Rust)
1. **Add to Rust code**:
```rust
return Err(tinymist_l10n::t!(
"compilation.invalid-syntax",
"Invalid syntax at line {line}",
line = line_number
).into());
```
2. **Extract messages**:
```bash
yarn build:l10n
```
3. **Add translations** in `locales/tinymist-rt.toml`:
```toml
[compilation.invalid-syntax]
en = "Invalid syntax at line {line}"
zh = "第 {line} 行语法错误"
fr = "Syntaxe invalide à la ligne {line}"
```
### Example 2: Adding a New UI Label (TypeScript)
1. **Add to TypeScript code**:
```typescript
const exportButton = vscode.window.createQuickPick();
exportButton.title = l10nMsg("Select Export Format");
```
2. **Extract messages**:
```bash
yarn build:l10n
```
3. **Add translations** in `locales/tinymist-vscode-rt.toml`:
```toml
["Select Export Format"]
en = "Select Export Format"
zh = "选择导出格式"
fr = "Sélectionner le format d'exportation"
```
### Example 3: Complex Message with Multiple Parameters
```rust
let message = tinymist_l10n::t!(
"preview.compilation-stats",
"Compiled {pages} pages in {duration}ms",
pages = page_count,
duration = compile_time
);
```
Corresponding translation:
```toml
[preview.compilation-stats]
en = "Compiled {pages} pages in {duration}ms"
zh = "在 {duration} 毫秒内编译了 {pages} 页"
fr = "Compilé {pages} pages en {duration}ms"
```
## Best Practices
### When to Localize
**DO localize:**
- User-facing error messages
- UI labels and buttons
- Status messages
- Command descriptions
- Configuration descriptions
**DON'T localize:**
- Log messages for developers
- Debug output
- Technical error codes
- File paths or URLs
- Code identifiers
### Message Design
1. **Keep messages concise** but informative
2. **Use parameters** for dynamic content instead of string concatenation
3. **Design for translation** - avoid culture-specific references
4. **Group related messages** using consistent key prefixes
### Key Naming
```rust
// Good: Hierarchical, descriptive
tinymist_l10n::t!("export.pdf.success", "PDF exported successfully")
tinymist_l10n::t!("export.pdf.error.permission", "Permission denied for PDF export")
// Bad: Flat, unclear
tinymist_l10n::t!("msg1", "PDF exported successfully")
tinymist_l10n::t!("err", "Permission denied")
```
## Troubleshooting
### Common Issues
1. **Build errors after adding localization**:
- Ensure proper import of `tinymist_l10n` or `l10nMsg`
- Check for typos in macro/function calls
- Run `yarn build:l10n` to extract new messages
2. **Missing translations**:
- Verify the key exists in the appropriate locale file
- Check if extraction was run after adding the message
- Ensure locale file syntax is valid TOML
3. **Parameter substitution not working**:
- Verify parameter names match between code and translation
- Check for missing or extra parameters in translations
### Testing Localization
1. **Test message extraction**:
```bash
yarn build:l10n
```
2. **Verify locale files** contain your new messages
3. **Test in different languages** by changing VSCode language settings
## File Structure Reference
```
locales/
├── README.md # Documentation
├── tinymist-vscode.toml # Manual VSCode translations
├── tinymist-vscode-rt.toml # Auto-extracted VSCode messages
└── tinymist-rt.toml # Auto-extracted Rust messages
crates/
├── tinymist-l10n/ # Localization library
└── */src/**/*.rs # Rust source files (use tinymist_l10n::t!)
editors/vscode/
├── src/l10n.ts # TypeScript l10n helper
└── src/**/*.ts # TypeScript source files (use l10nMsg)
scripts/
└── build-l10n.mjs # Message extraction script
```
## Advanced Usage
### Conditional Messages
```rust
let message = if is_error {
tinymist_l10n::t!("status.error", "Error occurred")
} else {
tinymist_l10n::t!("status.success", "Operation completed")
};
```
### Pluralization
Handle plurals with parameters:
```rust
let message = tinymist_l10n::t!(
"files.count",
"{count} file(s) processed",
count = file_count
);
```
Translation:
```toml
[files.count]
en = "{count} file(s) processed"
zh = "已处理 {count} 个文件"
fr = "{count} fichier(s) traité(s)"
```
Remember to run `yarn build:l10n` after adding any new localized messages to code, and only edit translation files manually for languages other than English.

View file

@ -1,128 +0,0 @@
This is a Rust+JavaScript repository. It builds:
- A Rust binary serves language features:
- `lsp`: Runs language server
- `dap`: Runs debug adapter
- `preview`: Runs preview server
- The JavaScript VS Code extension.
- The lua plugin for Neovim.
It is primarily responsible for providing integrated typst language service to various editors like VS Code, Neovim, Emacs, and Zed. Please follow these guidelines when contributing:
## Specialized Instructions
- **Localization**: See [copilot-instructions-l10n.md](./copilot-instructions-l10n.md) for comprehensive guidance on adding, updating, and maintaining localization in the project.
## Code Standards
### Keep Good PR Title
Determine a good PR prefix **only** by the PR description before work. Add a prefix to indicate what kind of release this pull request corresponds to. For reference, see https://www.conventionalcommits.org/
Available types:
- dev
- feat
- fix
- docs
- style
- refactor
- perf
- test
- build
- ci
- chore
- revert
### Required Before Each Commit
- Run `yarn fmt` to format Rust/JavaScript files
- This will run formatters on all necessary files to maintain consistent style
### Development Flow
- Build Server: `cargo build`
- Build VS Code Extension: `cd editors/vscode && yarn build`
- Full CI check: `cargo clippy --workspace --all-targets`
- Test Server: `cargo test --workspace -- --skip=e2e`
Note that, in the envoironment where network is not available (copilot or nix actions), we should also skip following tests:
```
completion::tests::test_pkgs
docs::package::tests::cetz
docs::package::tests::fletcher
docs::package::tests::tidy
docs::package::tests::touying
```
## Repository Structure
- `crates/`: rust crates for the server and related functionality
- `editors/vscode/`: VS Code extension code
- `editors/neovim/`: Lua plugin for Neovim (canonical implementation - see [CONTRIBUTING.md](editors/neovim/CONTRIBUTING.md) and [Specification.md](editors/neovim/Specification.md))
- `tools/editor-tools`: utility GUI tools for typst
- `tools/typst-preview-frontend`: Preview GUI for typst
- `docs/`: documentation for the project
- `locales/`: localization files for the entire project
- `tests/`: integration tests for the server and editors
- `syntaxes/`: textmate syntax definitions for typst
## Key Guidelines
1. Follow Rust and JavaScript best practices and idiomatic patterns
2. Maintain existing code structure and organization
4. Write unit tests for new functionality. Use snapshot-based unit tests when possible.
5. Document public APIs and complex logic in code comments
## Editor Integration Guidelines
### Neovim Canonical Implementation
The Neovim plugin in `editors/neovim/` serves as the **canonical implementation** of a Tinymist editor language client. When working on editor integrations:
- **Reference Implementation**: Use the Neovim plugin as the reference for LSP client patterns, configuration handling, and event subscription mechanisms
- **Test Suite**: Refer to `editors/neovim/spec/` for comprehensive test coverage examples
- **Documentation**: See [editors/neovim/Specification.md](editors/neovim/Specification.md) for complete API and functionality documentation
- **Development Workflow**: Use `./bootstrap.sh editor` for interactive testing and `./bootstrap.sh test` for automated validation
- **Contributing**: Follow patterns established in [editors/neovim/CONTRIBUTING.md](editors/neovim/CONTRIBUTING.md)
## Development Guidelines
### `tools/editor-tools`
The frontend-side and backend-side can be developed independently. For example, a data object passed from backend to frontend can be coded as `van.state<T>` as follows:
- Intermediate arguments:
```ts
const documentMetricsData = `:[[preview:DocumentMetrics]]:`;
const docMetrics = van.state<DocumentMetrics>(
documentMetricsData.startsWith(":") ? DOC_MOCK : JSON.parse(base64Decode(documentMetricsData)),
);
```
- Server-pushing arguments (e.g. `programTrace` in `tools/editor-tools/src/vscode.ts`):
```ts
export const programTrace = van.state<TraceReport | undefined>(undefined /* init value */);
export function setupVscodeChannel() {
if (vscodeAPI?.postMessage) {
// Handle messages sent from the extension to the webview
window.addEventListener("message", (event: any) => {
switch (event.data.type) {
case "traceData": {
programTrace.val = event.data.data;
break;
}
// other cases
}
});
}
}
```
- Tool request arguments (e.g. `requestSaveFontsExportConfigure` in `tools/editor-tools/src/vscode.ts`):
```ts
export function requestSaveFontsExportConfigure(data: fontsExportConfigure) {
if (vscodeAPI?.postMessage) {
vscodeAPI.postMessage({ type: "saveFontsExportConfigure", data });
}
}
```
`DOC_MOCK` is a mock data object for the frontend to display so that the frontend can be developed directly with `yarn dev`.

View file

@ -1,70 +0,0 @@
name: tinymist::announce
on:
workflow_call:
inputs:
tag:
description: Release Tag
required: true
type: string
workflow_dispatch:
inputs:
tag:
description: Release Tag
required: true
type: string
permissions:
"contents": "write"
env:
isNightly: ${{ ((!((!contains(inputs.tag, 'rc') && (endsWith(inputs.tag, '0') || endsWith(inputs.tag, '2') || endsWith(inputs.tag, '4') || endsWith(inputs.tag, '6') || endsWith(inputs.tag, '8')))))) }}
jobs:
build:
runs-on: "ubuntu-22.04"
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install dist
# we specify bash to get pipefail; it guards against the `curl` command
# failing. otherwise `sh` won't catch that `curl` returned non-0
shell: bash
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/Myriad-Dreamin/cargo-dist/releases/download/v0.28.6-tinymist.3/cargo-dist-installer.sh | sh"
- name: Install parse changelog
uses: taiki-e/install-action@parse-changelog
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 24
cache: 'yarn'
- name: Install deps
run: yarn install
- id: announce
name: "Generate announcement"
run: |
yarn draft-release ${{ inputs.tag }}
echo "draft-release ran successfully"
- name: "Upload announcement changelog"
uses: actions/upload-artifact@v4
with:
name: announcement-changelog.md
path: target/announcement-changelog.md
- name: "Upload announcement"
uses: actions/upload-artifact@v4
with:
name: announcement-dist.md
path: target/announcement-dist.md
- name: "Upload announcement"
uses: actions/upload-artifact@v4
with:
name: announcement.gen.md
path: target/announcement.gen.md
- name: Create GitHub Release
env:
PRERELEASE_FLAG: "${{ (fromJson(env.isNightly) && '--prerelease') || '' }}"
ANNOUNCEMENT_TITLE: "${{ steps.announce.outputs.tag }}"
RELEASE_COMMIT: "${{ github.sha }}"
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "Creating release for ${{ steps.announce.outputs.tag }} with PRERELEASE_FLAG=$PRERELEASE_FLAG (isNightly=$isNightly)"
gh release create "${{ steps.announce.outputs.tag }}" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file target/announcement.gen.md --draft=true

View file

@ -1,151 +0,0 @@
name: tinymist::auto_tag
on:
push:
branches:
- main
jobs:
auto-tag:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
token: ${{ secrets.REPO_TOKEN }}
fetch-depth: 0
- name: Get merged PR info
id: get-pr
run: |
COMMIT_SHA="${{ github.sha }}"
PR_NUMBER=$(gh pr list --state merged --limit 50 --json number,mergeCommit \
--jq ".[] | select(.mergeCommit.oid == \"$COMMIT_SHA\") | .number")
if [ -n "$PR_NUMBER" ]; then
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
echo "Found merged PR: #$PR_NUMBER"
else
echo "pr_number=" >> $GITHUB_OUTPUT
echo "No merged PR found for this commit"
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check for tag directive in merged PR
if: steps.get-pr.outputs.pr_number != ''
id: check-tag
uses: actions/github-script@v7
with:
script: |
const prNumber = '${{ steps.get-pr.outputs.pr_number }}';
if (!prNumber) {
console.log('No PR number found');
core.setOutput('tag_found', 'false');
return;
}
try {
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: parseInt(prNumber)
});
const prBody = pr.body || '';
console.log('PR Body:', prBody);
const tagRegex = /^\+tag\s+(v\d+\.\d+\.\d+(?:-[a-zA-Z0-9]+)?)/m;
const match = prBody.match(tagRegex);
if (match) {
const tagVersion = match[1];
console.log('Found tag directive:', tagVersion);
core.setOutput('tag_found', 'true');
core.setOutput('tag_version', tagVersion);
} else {
console.log('No tag directive found in merged PR');
core.setOutput('tag_found', 'false');
}
} catch (error) {
console.error('Error fetching PR:', error);
core.setOutput('tag_found', 'false');
}
- name: Check if tag already exists
if: steps.check-tag.outputs.tag_found == 'true'
id: check-existing-tag
run: |
TAG="${{ steps.check-tag.outputs.tag_version }}"
if git tag -l | grep -q "^$TAG$"; then
echo "tag_exists=true" >> $GITHUB_OUTPUT
echo "Tag $TAG already exists"
else
echo "tag_exists=false" >> $GITHUB_OUTPUT
echo "Tag $TAG does not exist, safe to create"
fi
- name: Create tag
if: steps.check-tag.outputs.tag_found == 'true' && steps.check-existing-tag.outputs.tag_exists == 'false'
run: |
TAG="${{ steps.check-tag.outputs.tag_version }}"
PR_NUMBER="${{ steps.get-pr.outputs.pr_number }}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "$TAG" -m "Auto-created tag $TAG from PR #$PR_NUMBER"
git push origin "$TAG"
echo "Created and pushed tag: $TAG"
- name: Comment on merged PR
if: steps.check-tag.outputs.tag_found == 'true' && steps.check-existing-tag.outputs.tag_exists == 'false'
uses: actions/github-script@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const tagVersion = '${{ steps.check-tag.outputs.tag_version }}';
const prNumber = '${{ steps.get-pr.outputs.pr_number }}';
const comment = `**Tag Created Successfully**
Tag \`${tagVersion}\` has been automatically created and pushed to the repository following the merge of this PR.
You can view the tag here: https://github.com/${{ github.repository }}/releases/tag/${tagVersion}`;
github.rest.issues.createComment({
issue_number: parseInt(prNumber),
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});
- name: Handle tag creation error
if: steps.check-tag.outputs.tag_found == 'true' && steps.check-existing-tag.outputs.tag_exists == 'true'
uses: actions/github-script@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const tagVersion = '${{ steps.check-tag.outputs.tag_version }}';
const prNumber = '${{ steps.get-pr.outputs.pr_number }}';
const actionUrl = `${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}`;
const comment = `**Tag Creation Failed**
Could not create tag \`${tagVersion}\`.
Please refer to [this action run](${actionUrl}) for more information.`;
github.rest.issues.createComment({
issue_number: parseInt(prNumber),
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});

View file

@ -1,96 +0,0 @@
name: tinymist::build::vsc_assets
on:
workflow_call:
env:
target: x86_64-unknown-linux-gnu
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 24
cache: 'yarn'
- name: Install deps
run: yarn install
- uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Download tinymist binary artifact
uses: actions/download-artifact@v4
with:
name: artifacts-build-local-${{ env.target }}
path: prebuilts
- name: Unzip tinymist binary artifact (Windows)
run: 7z x -y -oprebuilts prebuilts/tinymist-${{ env.target }}.zip
if: contains(env.target, 'windows')
- name: Unzip tinymist binary artifact (Linux)
run: |
tar -xvf prebuilts/tinymist-${{ env.target }}.tar.gz -C prebuilts
mv prebuilts/tinymist-${{ env.target }}/tinymist prebuilts/tinymist
if: ${{ !contains(env.target, 'windows') }}
- name: Download font assets
# use fonts in stable releases
run: |
mkdir -p assets/fonts/
curl -L https://github.com/Myriad-Dreamin/shiroa/releases/download/v0.1.2/font-assets.tar.gz | tar -xvz -C assets/fonts
curl -L https://github.com/Myriad-Dreamin/shiroa/releases/download/v0.1.0/charter-font-assets.tar.gz | tar -xvz -C assets/fonts
curl -L https://github.com/Myriad-Dreamin/shiroa/releases/download/v0.1.5/source-han-serif-font-assets.tar.gz | tar -xvz -C assets/fonts
- name: Download & install shiroa
run: |
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/Myriad-Dreamin/shiroa/releases/download/v0.3.1-rc3/shiroa-installer.sh | sh
- name: Build Book
run: |
shiroa build --font-path ./assets/typst-fonts/ --font-path ./assets/fonts/ --path-to-root /tinymist/ -w . docs/tinymist --mode=static-html
- name: Build PDF Documentation
run: |
prebuilts/tinymist compile --font-path assets/fonts --root . docs/tinymist/ebook.typ tinymist-docs.pdf
# todo: this is a bug
- name: Install PDF Documentation
run: |
mkdir -p editors/vscode/out/ contrib/html/editors/vscode/out/
cp tinymist-docs.pdf editors/vscode/out/tinymist-docs.pdf
cp tinymist-docs.pdf contrib/html/editors/vscode/out/tinymist-docs.pdf
- name: Upload PDF Documentation
uses: actions/upload-artifact@v4
with:
name: tinymist-docs.pdf
path: tinymist-docs.pdf
if-no-files-found: error
- name: Build typst-preview vscode extension
run: |
yarn
yarn run compile
working-directory: ./contrib/typst-preview/editors/vscode
- name: Build tinymist vscode extension
run: |
yarn
yarn run compile
working-directory: ./editors/vscode
- name: Pre-bundle tinymist vscode extension
uses: actions/upload-artifact@v4
with:
name: vscode-artifacts-tinymist
path: editors/vscode/out
- name: Pre-bundle tinymist vscode extension (L10n)
uses: actions/upload-artifact@v4
with:
name: vscode-artifacts-tinymist-l10n
path: |
editors/vscode/l10n/**/*
editors/vscode/package.nls.json
editors/vscode/package.nls.*.json
- name: Pre-bundle typst-preview vscode extension
uses: actions/upload-artifact@v4
with:
name: vscode-artifacts-typst-preview
path: contrib/typst-preview/editors/vscode/out

View file

@ -1,181 +0,0 @@
name: tinymist::build::vscode::main
on:
workflow_call:
jobs:
build:
strategy:
matrix:
include:
- os: windows-latest
rust-target: x86_64-pc-windows-msvc
platform: win32
arch: x64
regular_build: 'true'
- os: windows-latest
rust-target: aarch64-pc-windows-msvc
platform: win32
arch: arm64
- os: ubuntu-22.04
rust-target: x86_64-unknown-linux-gnu
platform: linux
arch: x64
regular_build: 'true'
- os: ubuntu-22.04
rust-target: aarch64-unknown-linux-gnu
platform: linux
arch: arm64
- os: ubuntu-22.04
rust-target: arm-unknown-linux-gnueabihf
platform: linux
arch: armhf
- os: macos-14
rust-target: x86_64-apple-darwin
platform: darwin
arch: x64
- os: macos-14
rust-target: aarch64-apple-darwin
platform: darwin
arch: arm64
regular_build: 'true'
name: build (${{ matrix.platform }}-${{ matrix.arch }})
runs-on: ${{ matrix.os }}
env:
target: ${{ matrix.platform }}-${{ matrix.arch }}
isRelease: ${{ (startsWith(github.ref, 'refs/tags/') && (!contains(github.ref, 'rc') && (endsWith(github.ref, '0') || endsWith(github.ref, '2') || endsWith(github.ref, '4') || endsWith(github.ref, '6') || endsWith(github.ref, '8')))) }}
isNightly: ${{ ((startsWith(github.ref, 'refs/tags/') && !((!contains(github.ref, 'rc') && (endsWith(github.ref, '0') || endsWith(github.ref, '2') || endsWith(github.ref, '4') || endsWith(github.ref, '6') || endsWith(github.ref, '8'))))) || (!startsWith(github.ref, 'refs/tags/') && matrix.regular_build == 'true')) }}
isTest: ${{ matrix.rust-target == 'x86_64-unknown-linux-gnu' || matrix.rust-target == 'x86_64-pc-windows-msvc' }}
isUniversal: ${{ matrix.rust-target == 'x86_64-unknown-linux-gnu' }}
steps:
- name: "Print Env"
run: |
echo "Running on ${{ matrix.os }}"
echo "Target: ${{ env.target }}"
echo "Is Release: ${{ fromJson(env.isRelease) }}"
echo "Is Nightly: ${{ fromJson(env.isNightly) }}"
echo "Is Test: ${{ fromJson(env.isTest) }}"
echo "Is Universal (No Server): ${{ fromJson(env.isUniversal) }}"
- uses: actions/checkout@v4
with:
submodules: recursive
if: (fromJson(env.isRelease) || fromJson(env.isNightly))
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'yarn'
if: (fromJson(env.isRelease) || fromJson(env.isNightly))
- name: Install deps
run: yarn install
if: (fromJson(env.isRelease) || fromJson(env.isNightly))
- name: Download tinymist binary artifact
uses: actions/download-artifact@v4
with:
name: artifacts-build-local-${{ matrix.rust-target }}
path: prebuilts
if: (fromJson(env.isRelease) || fromJson(env.isNightly))
- name: Unzip tinymist binary artifact (Windows)
run: 7z x -y -oprebuilts prebuilts/tinymist-${{ matrix.rust-target }}.zip
if: matrix.platform == 'win32' && (fromJson(env.isRelease) || fromJson(env.isNightly))
- name: Unzip tinymist binary artifact (Linux)
run: |
tar -xvf prebuilts/tinymist-${{ matrix.rust-target }}.tar.gz -C prebuilts
mv prebuilts/tinymist-${{ matrix.rust-target }}/tinymist prebuilts/tinymist
if: matrix.platform != 'win32' && (fromJson(env.isRelease) || fromJson(env.isNightly))
- name: Download VSC Assets
uses: actions/download-artifact@v4
with:
name: vscode-artifacts-tinymist
path: editors/vscode/out
if: (fromJson(env.isRelease) || fromJson(env.isNightly))
- name: Download VSC Assets (L10n)
uses: actions/download-artifact@v4
with:
name: vscode-artifacts-tinymist-l10n
path: editors/vscode
if: (fromJson(env.isRelease) || fromJson(env.isNightly))
- name: Download VSC Assets (Preview)
uses: actions/download-artifact@v4
with:
name: vscode-artifacts-typst-preview
path: contrib/typst-preview/editors/vscode/out
if: (fromJson(env.isRelease) || fromJson(env.isNightly))
- name: Copy binary to output directory
if: (fromJson(env.isRelease) || fromJson(env.isNightly))
run: |
cp "prebuilts/tinymist${{ ( matrix.platform == 'win32' ) && '.exe' || '' }}" "editors/vscode/out/"
cp "prebuilts/tinymist${{ ( matrix.platform == 'win32' ) && '.exe' || '' }}" "contrib/typst-preview/editors/vscode/out/"
cp "prebuilts/tinymist${{ ( matrix.platform == 'win32' ) && '.exe' || '' }}" "tinymist-${{ env.target }}${{ ( matrix.platform == 'win32' ) && '.exe' || '' }}"
- name: Package typst-preview extension
if: fromJson(env.isRelease)
run: yarn run package -- --target ${{ env.target }} -o typst-preview-${{ env.target }}.vsix
working-directory: ./contrib/typst-preview/editors/vscode
- name: Package tinymist extension
if: fromJson(env.isRelease)
run: yarn run package -- --target ${{ env.target }} -o tinymist-${{ env.target }}.vsix
working-directory: ./editors/vscode
- name: Package typst-preview extension (Nightly)
if: fromJson(env.isNightly)
run: yarn run package -- --target ${{ env.target }} -o typst-preview-${{ env.target }}.vsix --pre-release
working-directory: ./contrib/typst-preview/editors/vscode
- name: Package tinymist extension (Nightly)
if: fromJson(env.isNightly)
run: yarn run package -- --target ${{ env.target }} -o tinymist-${{ env.target }}.vsix --pre-release
working-directory: ./editors/vscode
- name: Upload binary artifact
if: (fromJson(env.isRelease) || fromJson(env.isNightly))
uses: actions/upload-artifact@v4
with:
name: tinymist-${{ env.target }}
path: tinymist-${{ env.target }}${{ fromJSON('["", ".exe"]')[matrix.platform == 'win32'] }}
- name: Upload typst-preview VSIX artifact
if: (fromJson(env.isRelease) || fromJson(env.isNightly))
uses: actions/upload-artifact@v4
with:
name: typst-preview-${{ env.target }}.vsix
path: contrib/typst-preview/editors/vscode/typst-preview-${{ env.target }}.vsix
- name: Upload VSIX artifact
if: (fromJson(env.isRelease) || fromJson(env.isNightly))
uses: actions/upload-artifact@v4
with:
name: tinymist-${{ env.target }}.vsix
path: editors/vscode/tinymist-${{ env.target }}.vsix
- name: Test tinymist extension
uses: coactions/setup-xvfb@v1
with:
run: yarn test
working-directory: ./editors/vscode
if: (fromJson(env.isRelease) || fromJson(env.isNightly)) && fromJson(env.isTest)
- name: Upload Tinymist Testing log
if: ${{ always() && ((fromJson(env.isRelease) || fromJson(env.isNightly)) && fromJson(env.isTest))}}
uses: actions/upload-artifact@v4
with:
name: tinymist-lsp-tests.${{ env.target }}.log
path: editors/vscode/e2e-workspaces/simple-docs/tinymist-lsp.log
# The universal target doesn't bundle the binary. Users of that must install
# tinymist by themselves.
- name: Remove server binary
if: fromJson(env.isUniversal)
run: rm "editors/vscode/out/tinymist"
- name: Package extension (Universal)
if: fromJson(env.isRelease) && fromJson(env.isUniversal)
run: yarn run package -- -o tinymist-universal.vsix
working-directory: ./editors/vscode
- name: Package extension (Universal, Nightly)
if: fromJson(env.isNightly) && fromJson(env.isUniversal)
run: yarn run package -- -o tinymist-universal.vsix --pre-release
working-directory: ./editors/vscode
- name: Upload tinymist VSIX artifact (Universal)
if: (fromJson(env.isRelease) || fromJson(env.isNightly)) && fromJson(env.isUniversal)
uses: actions/upload-artifact@v4
with:
name: tinymist-universal.vsix
path: editors/vscode/tinymist-universal.vsix

View file

@ -1,188 +0,0 @@
name: tinymist::build::vscode::others
on:
workflow_call:
env:
RUSTFLAGS: '-Dwarnings'
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER: arm-linux-gnueabihf-gcc
jobs:
build_alpine:
name: build extension (alpine-${{ matrix.arch }})
runs-on: ${{ matrix.runner }}
if: startsWith(github.ref, 'refs/tags/')
container:
image: rust:alpine
volumes:
- /usr/local/cargo/registry:/usr/local/cargo/registry
- /opt:/opt:rw,rshared
- /opt:/__e/node20:ro,rshared
strategy:
matrix:
include:
- arch: x64
target: alpine-x64
RUST_TARGET: x86_64-unknown-linux-musl
runner: ubuntu-24.04
- arch: arm64
target: alpine-arm64
RUST_TARGET: aarch64-unknown-linux-musl
runner: ubuntu-24.04-arm
env:
target: ${{ matrix.target }}
RUST_TARGET: ${{ matrix.RUST_TARGET }}
RUSTFLAGS: "-Dwarnings -C link-arg=-fuse-ld=lld -C target-feature=-crt-static"
isRelease: ${{ (startsWith(github.ref, 'refs/tags/') && (!contains(github.ref, 'rc') && (endsWith(github.ref, '0') || endsWith(github.ref, '2') || endsWith(github.ref, '4') || endsWith(github.ref, '6') || endsWith(github.ref, '8')))) }}
isNightly: ${{ ((startsWith(github.ref, 'refs/tags/') && !((!contains(github.ref, 'rc') && (endsWith(github.ref, '0') || endsWith(github.ref, '2') || endsWith(github.ref, '4') || endsWith(github.ref, '6') || endsWith(github.ref, '8')))))) }}
steps:
- name: Allow Linux musl containers on ARM64 runners
if: matrix.runner == 'ubuntu-24.04-arm'
run: |
sed -i "/^ID=/s/alpine/NotpineForGHA/" /etc/os-release
apk add nodejs --update-cache
mkdir /opt/bin
ln -s /usr/bin/node /opt/bin/node
- name: Install dependencies
# bash is required by setup-rust-toolchain
run: apk add --no-cache git clang lld musl-dev nodejs-current npm yarn binutils bash
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Install deps
run: yarn install
- name: Build typst-preview vscode extension
run: yarn run compile
working-directory: ./contrib/typst-preview/editors/vscode
- name: Build tinymist vscode extension
run: yarn run compile
working-directory: ./editors/vscode
- name: Build tinymist binary
run: |
cargo build --profile=gh-release --bin tinymist --target $RUST_TARGET
- name: Split debug symbols
run: |
cd target/$RUST_TARGET/gh-release
objcopy --compress-debug-sections --only-keep-debug "tinymist" "tinymist-${{ env.target }}.debug"
objcopy --strip-debug --add-gnu-debuglink="tinymist-${{ env.target }}.debug" "tinymist"
- name: Upload split debug symbols
uses: actions/upload-artifact@v4
with:
name: tinymist-${{ env.target }}.debug
path: target/${{ env.RUST_TARGET }}/gh-release/tinymist-${{ env.target }}.debug
- name: Copy binary to output directory
run: |
mkdir -p editors/vscode/out
cp "target/${{ env.RUST_TARGET }}/gh-release/tinymist" "editors/vscode/out/"
cp "target/${{ env.RUST_TARGET }}/gh-release/tinymist" "contrib/typst-preview/editors/vscode/out/"
cp "target/${{ env.RUST_TARGET }}/gh-release/tinymist" "tinymist-${{ env.target }}"
- name: Upload binary artifact
uses: actions/upload-artifact@v4
with:
name: tinymist-${{ env.target }}
path: tinymist-${{ env.target }}
- name: Download PDF Documentation
uses: actions/download-artifact@v4
with:
name: tinymist-docs.pdf
if: (fromJson(env.isRelease) || fromJson(env.isNightly))
- name: Install PDF Documentation
run: |
mkdir -p editors/vscode/out/
cp tinymist-docs.pdf editors/vscode/out/tinymist-docs.pdf
if: (fromJson(env.isRelease) || fromJson(env.isNightly))
- name: Package typst-preview extension
if: fromJson(env.isRelease)
run: yarn run package -- --target ${{ env.target }} -o typst-preview-${{ env.target }}.vsix
working-directory: ./contrib/typst-preview/editors/vscode
- name: Package extension
if: fromJson(env.isRelease)
run: yarn run package -- --target ${{ env.target }} -o tinymist-${{ env.target }}.vsix
working-directory: ./editors/vscode
- name: Package typst-preview extension (Nightly)
if: fromJson(env.isNightly)
run: yarn run package -- --target ${{ env.target }} -o typst-preview-${{ env.target }}.vsix --pre-release
working-directory: ./contrib/typst-preview/editors/vscode
- name: Package extension (Nightly)
if: fromJson(env.isNightly)
run: yarn run package -- --target ${{ env.target }} -o tinymist-${{ env.target }}.vsix --pre-release
working-directory: ./editors/vscode
- name: Upload typst-preview VSIX artifact
uses: actions/upload-artifact@v4
with:
name: typst-preview-${{ env.target }}.vsix
path: contrib/typst-preview/editors/vscode/typst-preview-${{ env.target }}.vsix
- name: Upload tinymist VSIX artifact
uses: actions/upload-artifact@v4
with:
name: tinymist-${{ env.target }}.vsix
path: editors/vscode/tinymist-${{ env.target }}.vsix
build_web:
name: build extension (web)
runs-on: ubuntu-latest
env:
target: web
RUST_TARGET: wasm32-unknown-unknown
isNightly: ${{ ((startsWith(github.ref, 'refs/tags/') && !((!contains(github.ref, 'rc') && (endsWith(github.ref, '0') || endsWith(github.ref, '2') || endsWith(github.ref, '4') || endsWith(github.ref, '6') || endsWith(github.ref, '8')))))) }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 24
cache: 'yarn'
- uses: actions-rust-lang/setup-rust-toolchain@v1
- uses: jetli/wasm-pack-action@v0.4.0
with:
version: "v0.13.1"
- name: Install deps
run: yarn install
- name: Build tinymist library
run: yarn build:web
working-directory: .
- name: Pack tinymist npm library
run: |
npm pack > package-name
mv $(cat package-name) tinymist-${{ env.target }}.tar.gz
working-directory: ./crates/tinymist
- name: Upload tinymist npm library
uses: actions/upload-artifact@v4
with:
name: tinymist-${{ env.target }}-npm
path: crates/tinymist/tinymist-${{ env.target }}.tar.gz
- name: Download PDF Documentation
uses: actions/download-artifact@v4
with:
name: tinymist-docs.pdf
- name: Install PDF Documentation
run: |
mkdir -p editors/vscode/out/
cp tinymist-docs.pdf editors/vscode/out/tinymist-docs.pdf
- name: Package extension
if: '!fromJson(env.isNightly)'
run: yarn run package -- --target ${{ env.target }} -o tinymist-${{ env.target }}.vsix
working-directory: ./editors/vscode
- name: Package extension (Nightly)
if: fromJson(env.isNightly)
run: yarn run package -- --target ${{ env.target }} -o tinymist-${{ env.target }}.vsix --pre-release
working-directory: ./editors/vscode
- name: Upload tinymist VSIX artifact
uses: actions/upload-artifact@v4
with:
name: tinymist-${{ env.target }}.vsix
path: editors/vscode/tinymist-${{ env.target }}.vsix

View file

@ -1,91 +0,0 @@
name: tinymist::build::vscode
on:
workflow_call:
inputs:
plan:
description: 'A description of the plan input'
required: true # or false, depending on whether the input is mandatory
type: string # or other appropriate type like boolean, number, etc.
env:
RUSTFLAGS: '-Dwarnings'
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER: arm-linux-gnueabihf-gcc
jobs:
build-vsc-assets:
uses: ./.github/workflows/build-vsc-assets.yml
build-vscode-main:
needs: [build-vsc-assets]
uses: ./.github/workflows/build-vscode-main.yml
build-vscode-others:
needs: [build-vsc-assets]
uses: ./.github/workflows/build-vscode-others.yml
release:
needs: [build-vscode-main, build-vscode-others] # , announce
runs-on: ubuntu-latest
if: success() && startsWith(github.ref, 'refs/tags/')
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: actions/download-artifact@v4
with:
path: artifacts
pattern: '{tinymist,typst-preview}-*'
- name: Display structure of downloaded files
run: ls -R artifacts
- uses: ncipollo/release-action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
artifacts: "artifacts/*/*"
allowUpdates: true
omitBodyDuringUpdate: true
omitDraftDuringUpdate: true
omitNameDuringUpdate: true
omitPrereleaseDuringUpdate: true
publish:
needs: [build-vscode-main, build-vscode-others] # , announce
runs-on: ubuntu-latest
env:
isRelease: ${{ (startsWith(github.ref, 'refs/tags/') && (!contains(github.ref, 'rc') && (endsWith(github.ref, '0') || endsWith(github.ref, '2') || endsWith(github.ref, '4') || endsWith(github.ref, '6') || endsWith(github.ref, '8')))) }}
isNightly: ${{ ((startsWith(github.ref, 'refs/tags/') && !((!contains(github.ref, 'rc') && (endsWith(github.ref, '0') || endsWith(github.ref, '2') || endsWith(github.ref, '4') || endsWith(github.ref, '6') || endsWith(github.ref, '8')))))) }}
if: success() && startsWith(github.ref, 'refs/tags/') && !contains(github.ref, 'rc')
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: actions/download-artifact@v4
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 24
cache: 'yarn'
- name: Install deps
run: yarn install
- name: Deploy to VS Code Marketplace
if: fromJson(env.isRelease)
run: npx @vscode/vsce publish --packagePath $(find . -type f -iname 'tinymist-*.vsix') --skip-duplicate
env:
VSCE_PAT: ${{ secrets.VSCODE_MARKETPLACE_TOKEN }}
- name: Deploy to OpenVSX
if: fromJson(env.isRelease)
run: npx ovsx publish --packagePath $(find . -type f -iname 'tinymist-*.vsix') --skip-duplicate
env:
OVSX_PAT: ${{ secrets.OPENVSX_ACCESS_TOKEN }}
- name: Deploy to VS Code Marketplace (Nightly)
if: fromJson(env.isNightly)
run: npx @vscode/vsce publish --packagePath $(find . -type f -iname 'tinymist-*.vsix') --skip-duplicate --pre-release
env:
VSCE_PAT: ${{ secrets.VSCODE_MARKETPLACE_TOKEN }}
- name: Deploy to OpenVSX (Nightly)
if: fromJson(env.isNightly)
run: npx ovsx publish --packagePath $(find . -type f -iname 'tinymist-*.vsix') --skip-duplicate --pre-release
env:
OVSX_PAT: ${{ secrets.OPENVSX_ACCESS_TOKEN }}

View file

@ -1,67 +0,0 @@
name: tinymist::check-e2e
on:
workflow_call:
inputs:
plan:
description: 'A description of the plan input'
required: true # or false, depending on whether the input is mandatory
type: string # or other appropriate type like boolean, number, etc.
env:
RUSTFLAGS: '-Dwarnings'
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER: arm-linux-gnueabihf-gcc
jobs:
checks-e2e:
strategy:
matrix:
include:
- os: windows-2022
rust-target: x86_64-pc-windows-msvc
platform: win32
arch: x64
- os: windows-latest
rust-target: x86_64-pc-windows-msvc
platform: win32
arch: x64
- os: ubuntu-22.04
rust-target: x86_64-unknown-linux-gnu
platform: linux
arch: x64
- os: ubuntu-latest
rust-target: x86_64-unknown-linux-gnu
platform: linux
arch: x64
- os: macos-latest
rust-target: aarch64-apple-darwin
platform: darwin
arch: arm64
name: E2E Tests (${{ matrix.platform }}-${{ matrix.arch }} on ${{ matrix.os }})
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Download tinymist binary artifact
uses: actions/download-artifact@v4
with:
name: artifacts-build-local-${{ matrix.rust-target }}
path: editors/vscode/out
- name: Unzip tinymist binary artifact (Windows)
run: 7z x -y -oeditors/vscode/out editors/vscode/out/tinymist-${{ matrix.rust-target }}.zip
if: matrix.platform == 'win32'
- name: Unzip tinymist binary artifact (Linux)
run: |
tar -xvf editors/vscode/out/tinymist-${{ matrix.rust-target }}.tar.gz -C editors/vscode/out
mv editors/vscode/out/tinymist-${{ matrix.rust-target }}/tinymist editors/vscode/out/tinymist
if: matrix.platform != 'win32'
- name: Test GLIBC
run: node ./scripts/test-glibc.mjs editors/vscode/out/tinymist
if: matrix.platform != 'win32'
- name: Test Tinymist (E2E)
run: cargo test -p tests -- e2e
- name: Upload Tinymist E2E Test Snapshot
if: always()
uses: actions/upload-artifact@v4
with:
name: e2e-snapshot-${{ matrix.rust-target }}-${{ matrix.os }}
path: target/e2e

View file

@ -1,138 +0,0 @@
name: tinymist::ci
on:
push:
branches:
- main
- nightly
tags:
- "*"
pull_request:
types: [opened, synchronize]
branches:
- main
- nightly
workflow_dispatch:
env:
RUSTFLAGS: '-Dwarnings'
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER: arm-linux-gnueabihf-gcc
jobs:
pre_build:
permissions:
actions: write
contents: read
name: Duplicate Actions Detection
runs-on: ubuntu-latest
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
cancel_others: "true"
checks-linux:
name: Check Clippy, Formatting, Completion, Documentation, and Tests (Linux)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: typst-community/setup-typst@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
components: clippy, rustfmt
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 24
cache: 'yarn'
- name: Install deps
run: yarn install
- name: Check and build assets
run: |
yarn build:preview
yarn build:l10n
- run: cargo clippy --workspace --all-targets
- run: scripts/feature-testing.sh
- run: cargo fmt --check --all
- run: cargo doc --workspace --no-deps
- run: yarn build:typlite
- run: node ./scripts/link-docs.mjs --check
- name: Generate completions
run: |
mkdir -p completions/{zsh,bash,fish/vendor_completions.d,elvish/lib,nushell/vendor/autoload,powershell}/
cargo run --bin tinymist -- completion zsh > completions/zsh/_tinymist
cargo run --bin tinymist -- completion bash > completions/bash/tinymist
cargo run --bin tinymist -- completion fish > completions/fish/vendor_completions.d/tinymist.fish
cargo run --bin tinymist -- completion elvish > completions/elvish/lib/tinymist.elv
cargo run --bin tinymist -- completion nushell > completions/nushell/vendor/autoload/tinymist.nu
cargo run --bin tinymist -- completion powershell > completions/powershell/tinymist.ps1
tar -czvf tinymist-completions.tar.gz completions
- name: upload completions
uses: actions/upload-artifact@v4
with:
name: tinymist-completion-scripts
path: tinymist-completions.tar.gz
- name: Test tinymist
run: cargo test --workspace -- --skip=e2e
- name: Test Lockfile (Prepare)
run: ./scripts/test-lock.sh
- name: Test Lockfile (Check)
run: cargo test --package tinymist --lib -- route::tests --show-output --ignored
checks-windows:
name: Check Minimum Rust version and Tests (Windows)
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.89.0 # check-min-version
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 24
- name: Install deps
run: yarn install
- name: Check Rust Version
run: yarn check-msrv
- name: Check and build assets
run: |
yarn build:preview
yarn build:l10n
- run: cargo check --workspace
- name: Test tinymist
run: cargo test --workspace -- --skip=e2e
prepare-build:
runs-on: "ubuntu-latest"
outputs:
tag: ${{ steps.tag.outputs.tag }}
steps:
- uses: actions/checkout@v4
- id: tag # get the tag from package.json
run:
echo "tag=v$(jq -r '.version' editors/vscode/package.json)" >> $GITHUB_OUTPUT
- name: Show tag
run: echo "Tag is ${{ steps.tag.outputs.tag }}"
announce:
needs: [prepare-build]
permissions:
contents: write
uses: ./.github/workflows/announce.yml
if: ${{ startsWith(github.ref, 'refs/tags/') }}
secrets: inherit
with:
tag: ${{ needs.prepare-build.outputs.tag }}
build:
needs: [prepare-build] # , announce
permissions:
contents: write
uses: ./.github/workflows/release.yml
secrets: inherit
with:
tag: ${{ (startsWith(github.ref, 'refs/tags/') && needs.prepare-build.outputs.tag) || '' }}
targets: ${{ (!startsWith(github.ref, 'refs/tags/') && 'aarch64-apple-darwin,x86_64-pc-windows-msvc,x86_64-unknown-linux-gnu') || 'all' }}

View file

@ -1,62 +0,0 @@
name: tinymist::detect_pr_tag
on:
pull_request:
types: [opened, edited]
branches:
- main
jobs:
detect-tag:
runs-on: ubuntu-latest
steps:
- name: Check tag in PR body
id: check-tag
uses: actions/github-script@v7
with:
script: |
const prBody = context.payload.pull_request.body || '';
console.log('PR Body:', prBody);
const tagRegex = /^\+tag\s+(v\d+\.\d+\.\d+(?:-[a-zA-Z0-9]+)?)/m;
const match = prBody.match(tagRegex);
if (match) {
const tagVersion = match[1];
console.log('Found tag:', tagVersion);
core.setOutput('tag_found', 'true');
core.setOutput('tag_version', tagVersion);
} else {
console.log('No tag found in PR description');
core.setOutput('tag_found', 'false');
}
- name: Comment on PR
if: steps.check-tag.outputs.tag_found == 'true'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const tagVersion = '${{ steps.check-tag.outputs.tag_version }}';
const comment = `**Tag Detection Notice**
This PR contains a tag directive: \`+tag ${tagVersion}\`
If this PR is merged, it will automatically create tag \`${tagVersion}\` on the main branch.
Please ensure before merging:
- [ ] **Cargo.toml & Cargo.lock**: No \`git\` dependencies with \`branch\`, use \`tag\` or \`rev\` dependencies instead
- [ ] **Publish tokens**: Both \`VSCODE_MARKETPLACE_TOKEN\` and \`OPENVSX_ACCESS_TOKEN\` are valid and not expired
- [ ] **Version updates**: All version numbers in \`Cargo.toml\`, \`package.json\` and other files have been updated consistently
- [ ] **Changelog**: \`editors/vscode/CHANGELOG.md\` has been updated with correct format
- [ ] **tinymist-assets**: If needed, the crate has been published and version updated in \`Cargo.toml\`
`
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});

View file

@ -1,79 +0,0 @@
name: tinymist::gh_pages
on:
push:
branches:
- main
workflow_dispatch:
permissions:
pages: write
id-token: write
contents: read
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: 'pages'
cancel-in-progress: false
jobs:
build-gh-pages:
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Checkout
uses: actions/checkout@v3
- run: git submodule update --init --recursive
- name: Make directories
run: |
if [ ! -d "${XDG_DATA_HOME:-$HOME/.local/share}" ]; then
echo "Creating data directory: ${XDG_DATA_HOME:-$HOME/.local/share}"
mkdir -p ${XDG_DATA_HOME:-$HOME/.local/share}
else
echo "Data directory already exists: ${XDG_DATA_HOME:-$HOME/.local/share}"
fi
if [ ! -d "${XDG_CACHE_HOME:-$HOME/.cache}" ]; then
echo "Creating cache directory: ${XDG_CACHE_HOME:-$HOME/.cache}"
mkdir -p ${XDG_CACHE_HOME:-$HOME/.cache}
else
echo "Cache directory already exists: ${XDG_CACHE_HOME:-$HOME/.cache}"
fi
- name: Download font assets
# use fonts in stable releases
run: |
mkdir -p assets/fonts/
curl -L https://github.com/Myriad-Dreamin/shiroa/releases/download/v0.1.2/font-assets.tar.gz | tar -xvz -C assets/fonts
curl -L https://github.com/Myriad-Dreamin/shiroa/releases/download/v0.1.0/charter-font-assets.tar.gz | tar -xvz -C assets/fonts
curl -L https://github.com/Myriad-Dreamin/shiroa/releases/download/v0.1.5/source-han-serif-font-assets.tar.gz | tar -xvz -C assets/fonts
- name: Download & install shiroa
run: |
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/Myriad-Dreamin/shiroa/releases/download/v0.3.1-rc3/shiroa-installer.sh | sh
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 24
cache: 'yarn'
- name: Install deps
run: yarn install
- name: Build Preview Html
run: |
yarn build:preview
- name: Build Book
run: |
shiroa build --font-path ./assets/typst-fonts/ --font-path ./assets/fonts/ --path-to-root /tinymist/ -w . docs/tinymist --mode=static-html
- name: Build Cargo Docs
run: |
cargo doc --workspace --no-deps
cp -r target/doc dist/tinymist/rs
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
# Upload `/github-pages` sub directory
path: './dist/tinymist'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

View file

@ -1,59 +0,0 @@
name: tinymist::lint_pr_title
on:
pull_request:
types: [opened, edited, synchronize]
permissions:
pull-requests: write
jobs:
main:
name: Validate PR title
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v5
id: lint_pr_title
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
# Configure which types are allowed (newline-delimited).
# Default: https://github.com/commitizen/conventional-commit-types
# extraType: dev: internal development
types: |
dev
feat
fix
docs
style
refactor
perf
test
build
ci
chore
revert
ignoreLabels: |
bot
ignore-semantic-pull-request
- uses: marocchino/sticky-pull-request-comment@v2
# When the previous steps fails, the workflow would stop. By adding this
# condition you can continue the execution with the populated error message.
if: always() && (steps.lint_pr_title.outputs.error_message != null)
with:
header: pr-title-lint-error
message: |
Hey there and thank you for opening this pull request! 👋🏼
We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted.
Details:
```
${{ steps.lint_pr_title.outputs.error_message }}
```
# Delete a previous comment when the issue has been resolved
- if: ${{ steps.lint_pr_title.outputs.error_message == null }}
uses: marocchino/sticky-pull-request-comment@v2
with:
header: pr-title-lint-error
delete: true

View file

@ -1,42 +0,0 @@
name: tinymist::assets::publish
on:
workflow_dispatch:
env:
RUSTFLAGS: '-Dwarnings'
jobs:
publish-crates:
name: build
runs-on: ubuntu-22.04
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache: false
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 24
cache: 'yarn'
- name: Install llvm
run: |
sudo apt-get update
sudo apt-get install llvm
- name: Install deps
run: yarn install
- name: Check and build assets
run: |
yarn build:preview
yarn build:l10n
- name: Publish crates
run: |
cargo publish --allow-dirty --no-verify -p tinymist-assets || true
- name: Verifies crate health (Optional)
run: |
cargo publish --allow-dirty --dry-run -p tinymist-assets

View file

@ -1,72 +0,0 @@
name: tinymist::crates::publish
on:
push:
tags:
- "*"
workflow_dispatch:
env:
RUSTFLAGS: '-Dwarnings'
jobs:
publish-crates:
name: build
runs-on: ubuntu-latest
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
# https://github.com/dtolnay/rust-toolchain/issues/133
# https://github.com/rust-lang/rustup/issues/3635
# Only needed if your action will run two or more rust
# commands concurrently, otherwise rustup will lazily
# install your rust-toolchain.toml when needed:
- name: 'Install from rust-toolchain.toml'
run: rustup show
- name: Install llvm
run: |
sudo apt-get update
sudo apt-get install llvm
- name: Publish crates
run: |
cargo publish --no-verify -p typst-shim || true
cargo publish --no-verify -p tinymist-derive || true
cargo publish --no-verify -p tinymist-l10n || true
cargo publish --no-verify -p tinymist-std || true
cargo publish --no-verify -p sync-ls || true
cargo publish --no-verify -p tinymist-package || true
cargo publish --no-verify -p tinymist-vfs || true
cargo publish --no-verify -p tinymist-world || true
cargo publish --no-verify -p tinymist-analysis || true
cargo publish --no-verify -p tinymist-task || true
cargo publish --no-verify -p tinymist-project || true
cargo publish --no-verify -p typlite || true
cargo publish --no-verify -p crityp || true
cargo publish --no-verify -p tinymist-debug || true
cargo publish --no-verify -p tinymist-lint || true
cargo publish --no-verify -p tinymist-query || true
cargo publish --no-verify -p tinymist-render || true
cargo publish --no-verify -p tinymist-preview || true
cargo publish --no-verify -p tinymist || true
cargo publish --no-verify -p tinymist-cli || true
- name: Verifies crate health (Optional)
run: |
cargo publish --dry-run -p typst-shim
cargo publish --dry-run -p tinymist-derive
cargo publish --dry-run -p tinymist-l10n
cargo publish --dry-run -p tinymist-std
cargo publish --dry-run -p sync-ls
cargo publish --dry-run -p tinymist-vfs
cargo publish --dry-run -p tinymist-package
cargo publish --dry-run -p tinymist-world
cargo publish --dry-run -p tinymist-task --features no-content-hint
cargo publish --dry-run -p tinymist-project --features no-content-hint
cargo publish --dry-run -p typlite
cargo publish --dry-run -p crityp
# needs patched typst
# cargo publish --dry-run -p tinymist-analysis

View file

@ -1,350 +0,0 @@
name: Nightly Release
on:
schedule:
- cron: '0 0 * * *'
- cron: '0 23 * * *'
workflow_dispatch:
inputs:
release_type:
description: 'Release type'
required: true
default: 'nightly'
type: choice
options:
- nightly
- canary
jobs:
check-and-release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: nightly
token: ${{ secrets.REPO_TOKEN }}
fetch-depth: 0
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'yarn'
- name: Install deps
run: yarn install
- name: Setup Git
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Determine release type
id: release_type
run: |
if [[ "${{ github.event_name }}" == "schedule" ]]; then
if [[ "${{ github.event.schedule }}" == "0 0 * * *" ]]; then
echo "release_type=nightly" >> $GITHUB_ENV
else
echo "release_type=canary" >> $GITHUB_ENV
fi
else
echo "release_type=${{ github.event.inputs.release_type }}" >> $GITHUB_ENV
fi
- name: Check for updates
id: check_updates
run: |
echo "Checking for updates in dependency repositories..."
# Get current revs using script
eval "$(node $GITHUB_WORKSPACE/scripts/nightly-utils.mjs . get-current-revs)"
# Get latest revs
latest_typst_rev=$(curl -s "https://api.github.com/repos/ParaN3xus/typst/commits/nightly" | jq -r '.sha')
latest_typst_content_hint_rev=$(curl -s "https://api.github.com/repos/ParaN3xus/typst/commits/nightly-content-hint" | jq -r '.sha')
latest_reflexo_rev=$(curl -s "https://api.github.com/repos/ParaN3xus/typst.ts/commits/nightly" | jq -r '.sha')
latest_typstyle_rev=$(curl -s "https://api.github.com/repos/ParaN3xus/typstyle/commits/nightly" | jq -r '.sha')
latest_typst_ansi_hl_rev=$(curl -s "https://api.github.com/repos/ParaN3xus/typst-ansi-hl/commits/nightly" | jq -r '.sha')
echo "Current revs: typst=$current_typst_rev, typst.ts=$current_reflexo_rev, typstyle=$current_typstyle_rev, hl=$current_typst_ansi_hl_rev"
echo "Latest revs: typst=$latest_typst_rev, typst.ts=$latest_reflexo_rev, typstyle=$latest_typstyle_rev, hl=$latest_typst_ansi_hl_rev"
# Check for updates
need_update=false
if [[ "$current_typst_rev" != "$latest_typst_rev" ]] || [[ -z "$current_typst_rev" ]]; then
echo "Typst needs update"
need_update=true
fi
if [[ "$current_reflexo_rev" != "$latest_reflexo_rev" ]] || [[ -z "$current_reflexo_rev" ]]; then
echo "Typst.ts needs update"
need_update=true
fi
if [[ "$current_typstyle_rev" != "$latest_typstyle_rev" ]] || [[ -z "$current_typstyle_rev" ]]; then
echo "Typstyle needs update"
need_update=true
fi
if [[ "$current_typst_ansi_hl_rev" != "$latest_typst_ansi_hl_rev" ]] || [[ -z "$current_typst_ansi_hl_rev" ]]; then
echo "Typst-ansi-hl needs update"
need_update=true
fi
current_version=$(grep '^version = ' Cargo.toml | head -1 | cut -d'"' -f2)
echo "current_version=$current_version" >> $GITHUB_ENV
# Updates can only be performed when releasing an RC.
# When an RC has been released and there are no subsequent updates,
# this indicates that the RC release was successful. Only at this
# point will the nightly release be published.
need_release=false
if [ "$release_type" = "nightly" ]; then
if [ "$need_update" = "false" ] && echo "$current_version" | grep -q -- '-rc[0-9]\+$'; then
echo "RC version detected with no updates needed, nightly release condition met"
need_release=true
else
echo "Nightly release condition not met (requires stable RC version)"
fi
elif [ "$release_type" = "canary" ]; then
if [ "$need_update" = "true" ]; then
echo "Code updates detected, canary release condition met"
need_release=true
else
echo "No code updates, skipping canary release"
fi
fi
echo "Final decision: need_release=$need_release"
echo "need_release=$need_release" >> $GITHUB_OUTPUT
echo "latest_typst_rev=$latest_typst_rev" >> $GITHUB_ENV
echo "latest_typst_content_hint_rev=$latest_typst_content_hint_rev" >> $GITHUB_ENV
- name: Calculate new version
id: version
if: steps.check_updates.outputs.need_release == 'true'
run: |
echo "Current version: $current_version"
echo "Release type: $release_type"
new_version=$(node $GITHUB_WORKSPACE/scripts/nightly-utils.mjs . calculate-version "$current_version" "$release_type")
echo "New version: $new_version"
echo "new_version=$new_version" >> $GITHUB_ENV
- uses: actions-rust-lang/setup-rust-toolchain@v1
if: steps.check_updates.outputs.need_release == 'true'
- name: Get typst information
id: typst_info
if: steps.check_updates.outputs.need_release == 'true'
run: |
# Clone typst repository
git clone --depth 50 --single-branch --branch nightly-content-hint \
--filter=blob:limit=1k https://github.com/ParaN3xus/typst.git /tmp/typst
cd /tmp/typst
git checkout nightly-content-hint
# Get version
typst_version=$(grep '^version = ' Cargo.toml | head -1 | cut -d'"' -f2)
typst_assets_rev=$(grep 'typst-assets.*git' Cargo.toml | grep 'rev = ' | cut -d'"' -f4)
echo "typst_version=$typst_version" >> $GITHUB_ENV
echo "typst_assets_rev=$typst_assets_rev" >> $GITHUB_ENV
# Get base commit
git remote add upstream https://github.com/typst/typst.git && git fetch upstream main --prune
typst_base_commit=$(git merge-base HEAD upstream/main 2>/dev/null)
typst_base_msg=$(git --no-pager log --format="%s" -1 $typst_base_commit)
echo "typst_base_commit=$typst_base_commit" >> $GITHUB_ENV
echo "typst_base_msg=$typst_base_msg" >> $GITHUB_ENV
- name: Update typst dependencies in tinymist
if: steps.check_updates.outputs.need_release == 'true'
run: |
node $GITHUB_WORKSPACE/scripts/nightly-utils.mjs . update-typst-deps \
"$typst_version" \
"$typst_assets_rev"
revs_json=$(cat <<EOF
{
"typst": "${latest_typst_rev}"
}
EOF
)
node $GITHUB_WORKSPACE/scripts/nightly-utils.mjs . update-patch-revs "$revs_json"
- name: Bump world crates version
if: steps.check_updates.outputs.need_release == 'true'
run: |
# node $GITHUB_WORKSPACE/scripts/nightly-utils.mjs . bump-world-crates "$new_version"
cargo update
git add -A
git commit -m "build: bump world crates to $new_version" || true
git push origin nightly
world_commit=$(git rev-parse HEAD)
echo "world_commit=$world_commit" >> $GITHUB_ENV
- name: Update typst.ts
if: steps.check_updates.outputs.need_release == 'true'
run: |
# Clone typst.ts
git clone https://${{ secrets.NIGHTLY_REPO_TOKEN }}@github.com/ParaN3xus/typst.ts.git /tmp/typst.ts
cd /tmp/typst.ts
git checkout nightly
new_version="$new_version"
# node $GITHUB_WORKSPACE/scripts/nightly-utils.mjs . update-world-crates "$new_version"
# Update typst dependencies
node $GITHUB_WORKSPACE/scripts/nightly-utils.mjs . update-typst-deps \
"$typst_version" \
"$typst_assets_rev"
# Update patches
revs_json=$(cat <<EOF
{
"tinymist": "${world_commit}",
"typst": "${latest_typst_content_hint_rev}"
}
EOF
)
node $GITHUB_WORKSPACE/scripts/nightly-utils.mjs . update-patch-revs "$revs_json"
cargo update
git add -A
git commit -m "build: update tinymist and typst" || true
git push origin nightly
reflexo_commit=$(git rev-parse HEAD)
echo "reflexo_commit=$reflexo_commit" >> $GITHUB_ENV
- name: Update typstyle
if: steps.check_updates.outputs.need_release == 'true'
run: |
# Clone typstyle
git clone https://${{ secrets.NIGHTLY_REPO_TOKEN }}@github.com/ParaN3xus/typstyle.git /tmp/typstyle
cd /tmp/typstyle
git checkout nightly
# node $GITHUB_WORKSPACE/scripts/nightly-utils.mjs . update-world-crates "$new_version"
node $GITHUB_WORKSPACE/scripts/nightly-utils.mjs . update-typst-deps \
"$typst_version" \
"$typst_assets_rev"
# Update patches
revs_json=$(cat <<EOF
{
"tinymist": "${world_commit}",
"typst": "${latest_typst_rev}"
}
EOF
)
node $GITHUB_WORKSPACE/scripts/nightly-utils.mjs . update-patch-revs "$revs_json"
cargo update
git add -A
git commit -m "build: update tinymist to ${new_version}" || true
git push origin nightly
typstyle_commit=$(git rev-parse HEAD)
echo "typstyle_commit=$typstyle_commit" >> $GITHUB_ENV
- name: Update typst-ansi-hl
if: steps.check_updates.outputs.need_release == 'true'
run: |
# Clone typst-ansi-hl
git clone https://${{ secrets.NIGHTLY_REPO_TOKEN }}@github.com/ParaN3xus/typst-ansi-hl.git /tmp/typst-ansi-hl
cd /tmp/typst-ansi-hl
git checkout nightly
node $GITHUB_WORKSPACE/scripts/nightly-utils.mjs . update-typst-deps \
"$typst_version" \
"$typst_assets_rev"
# Update patches
revs_json=$(cat <<EOF
{
"typst": "${latest_typst_rev}"
}
EOF
)
node $GITHUB_WORKSPACE/scripts/nightly-utils.mjs . update-patch-revs "$revs_json"
cargo update
git add -A
git commit -m "build: update typst-syntax" || true
git push origin nightly
hl_commit=$(git rev-parse HEAD)
echo "hl_commit=$hl_commit" >> $GITHUB_ENV
- name: Update tinymist patches and versions
if: steps.check_updates.outputs.need_release == 'true'
run: |
# Update patch revisions using script
revs_json=$(cat <<EOF
{
"reflexo": "${reflexo_commit}",
"typst-ansi-hl": "${hl_commit}",
"typstyle": "${typstyle_commit}"
}
EOF
)
node $GITHUB_WORKSPACE/scripts/nightly-utils.mjs . update-patch-revs "$revs_json"
# Update main version
node $GITHUB_WORKSPACE/scripts/nightly-utils.mjs . update-main-version "$new_version"
- name: Update version files
if: steps.check_updates.outputs.need_release == 'true'
run: |
node $GITHUB_WORKSPACE/scripts/nightly-utils.mjs . update-version-files "$new_version"
- name: Generate changelog
if: steps.check_updates.outputs.need_release == 'true'
run: |
tinymist_base_commit=$(git merge-base HEAD origin/main)
tinymist_base_msg=$(git --no-pager log --format="%s" -1 $tinymist_base_commit)
node $GITHUB_WORKSPACE/scripts/nightly-utils.mjs . generate-changelog \
"$new_version" \
"$tinymist_base_commit" \
"$tinymist_base_msg" \
"$latest_typst_rev" \
"$typst_base_commit" \
"$typst_base_msg"
- name: Final commit and tag
if: steps.check_updates.outputs.need_release == 'true'
run: |
new_version="$new_version"
cargo update
git add -A
git commit -m "build: bump version to ${new_version}"
bump_commit=$(git rev-parse HEAD)
git push origin nightly
curl -L \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.REPO_TOKEN }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/${{ github.repository }}/git/refs \
-d "{\"ref\":\"refs/tags/v${new_version}\",\"sha\":\"${bump_commit}\"}"
echo "Successfully released tinymist ${new_version}!"
- name: No updates needed
if: steps.check_updates.outputs.need_release != 'true'
run: |
echo "No updates needed. All dependencies are up to date."

View file

@ -1,341 +1,329 @@
# This file was autogenerated by dist: https://github.com/astral-sh/cargo-dist
#
# Copyright 2022-2024, axodotdev
# Copyright 2025 Astral Software Inc.
# SPDX-License-Identifier: MIT or Apache-2.0
#
# CI that:
#
# * checks for a Git Tag that looks like a release
# * builds artifacts with dist (archives, installers, hashes)
# * uploads those artifacts to temporary workflow zip
# * on success, uploads the artifacts to a GitHub Release
#
# Note that a GitHub Release with this tag is assumed to exist as a draft
# with the appropriate title/body, and will be undrafted for you.
name: Release
permissions:
"contents": "write"
# This task will run whenever you workflow_dispatch with a tag that looks like a version
# like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc.
# Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where
# PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION
# must be a Cargo-style SemVer Version (must have at least major.minor.patch).
#
# If PACKAGE_NAME is specified, then the announcement will be for that
# package (erroring out if it doesn't have the given version or isn't dist-able).
#
# If PACKAGE_NAME isn't specified, then the announcement will be for all
# (dist-able) packages in the workspace with that version (this mode is
# intended for workspaces with only one dist-able package, or with all dist-able
# packages versioned/released in lockstep).
#
# If you push multiple tags at once, separate instances of this workflow will
# spin up, creating an independent announcement for each one. However, GitHub
# will hard limit this to 3 tags per commit, as it will assume more tags is a
# mistake.
#
# If there's a prerelease-style suffix to the version, then the release(s)
# will be marked as a prerelease.
# stolen from https://github.com/nvarner/tinymist/blob/master/.github/workflows/release.yml
name: CI
on:
workflow_call:
inputs:
tag:
description: Release Tag
required: true
default: dry-run
type: string
targets:
description: Targets to build
required: true
default: all
type: string
push:
branches:
- main
tags:
- "*"
pull_request:
types: [opened, synchronize]
branches:
- main
workflow_dispatch:
inputs:
tag:
description: Release Tag
required: true
default: dry-run
type: string
targets:
description: Targets to build
required: true
default: all
type: string
env:
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER: arm-linux-gnueabihf-gcc
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache"
jobs:
# Run 'dist plan' (or host) to determine what tasks we need to do
plan:
runs-on: "ubuntu-22.04"
outputs:
val: ${{ steps.plan.outputs.manifest }}
tag: ${{ (inputs.tag != 'dry-run' && inputs.tag) || '' }}
tag-flag: ${{ inputs.tag && inputs.tag != 'dry-run' && format('--tag={0}', inputs.tag) || '' }}
publishing: ${{ inputs.tag && inputs.tag != 'dry-run' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
submodules: recursive
- name: Install dist
# we specify bash to get pipefail; it guards against the `curl` command
# failing. otherwise `sh` won't catch that `curl` returned non-0
shell: bash
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/Myriad-Dreamin/cargo-dist/releases/download/v0.28.6-tinymist.3/cargo-dist-installer.sh | sh"
- name: Cache dist
uses: actions/upload-artifact@v4
with:
name: cargo-dist-cache
path: ~/.cargo/bin/dist
# sure would be cool if github gave us proper conditionals...
# so here's a doubly-nested ternary-via-truthiness to try to provide the best possible
# functionality based on whether this is a pull_request, and whether it's from a fork.
# (PRs run on the *source* but secrets are usually on the *target* -- that's *good*
# but also really annoying to build CI around when it needs secrets to work right.)
- id: plan
run: |
dist ${{ (inputs.tag && inputs.tag != 'dry-run' && format('host --steps=create --tag={0}', inputs.tag)) || 'plan' }} --output-format=json > plan-dist-manifest.json
echo "dist ran successfully"
cat plan-dist-manifest.json
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@v4
with:
name: artifacts-plan-dist-manifest
path: plan-dist-manifest.json
# Build and packages all the platform-specific things
build-local-artifacts:
name: build-local-artifacts (${{ join(matrix.targets, ', ') }})
# Let the initial task tell us to not run (currently very blunt)
needs:
- plan
if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || (fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') || (inputs.targets == 'all' || contains(inputs.targets, join(fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.targets, '')))) || inputs.tag == 'dry-run' }}
strategy:
fail-fast: false
# Target platforms/runners are computed by dist in create-release.
# Each member of the matrix has the following arguments:
#
# - runner: the github runner
# - dist-args: cli flags to pass to dist
# - install-dist: expression to run to install dist on the runner
#
# Typically there will be:
# - 1 "global" task that builds universal installers
# - N "local" tasks that build each platform's binaries and platform-specific installers
matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }}
runs-on: ${{ matrix.runner }}
container: ${{ matrix.container && matrix.container.image || null }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json
steps:
- name: enable windows longpaths
run: |
git config --global core.longpaths true
- uses: actions/checkout@v4
with:
persist-credentials: false
submodules: recursive
- name: Install Rust non-interactively if not already installed
if: ${{ matrix.container }}
run: |
if ! command -v cargo > /dev/null 2>&1; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
fi
- uses: swatinem/rust-cache@v2
with:
key: ${{ join(matrix.targets, '-') }}
cache-provider: ${{ matrix.cache_provider }}
- name: Install dist
run: ${{ matrix.install_dist.run }}
# Get the dist-manifest
- name: Fetch local artifacts
uses: actions/download-artifact@v4
with:
pattern: artifacts-*
path: target/distrib/
merge-multiple: true
- name: Install dependencies
run: |
${{ matrix.packages_install }}
- name: Build artifacts
run: |
# Actually do builds and make zips and whatnot
dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json
echo "dist ran successfully"
- id: cargo-dist
name: Post-build
# We force bash here just because github makes it really hard to get values up
# to "real" actions without writing to env-vars, and writing to env-vars has
# inconsistent syntax between shell and powershell.
shell: bash
run: |
# Parse out what we just built and upload it to scratch storage
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
dist print-upload-files-from-manifest --manifest dist-manifest.json >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: "Upload artifacts"
uses: actions/upload-artifact@v4
with:
name: artifacts-build-local-${{ join(matrix.targets, '_') }}
path: |
${{ steps.cargo-dist.outputs.paths }}
${{ env.BUILD_MANIFEST_NAME }}
# Build and package all the platform-agnostic(ish) things
build-global-artifacts:
needs:
- plan
- build-local-artifacts
runs-on: "ubuntu-22.04"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@v4
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
- name: Fetch local artifacts
uses: actions/download-artifact@v4
with:
pattern: artifacts-*
path: target/distrib/
merge-multiple: true
- id: cargo-dist
shell: bash
run: |
dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json
echo "dist ran successfully"
# Parse out what we just built and upload it to scratch storage
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: "Upload artifacts"
uses: actions/upload-artifact@v4
with:
name: artifacts-build-global
path: |
${{ steps.cargo-dist.outputs.paths }}
${{ env.BUILD_MANIFEST_NAME }}
custom-build-vscode:
needs:
- plan
- build-local-artifacts
uses: ./.github/workflows/build-vscode.yml
with:
plan: ${{ needs.plan.outputs.val }}
secrets: inherit
pre_build:
permissions:
"contents": "write"
custom-ci-check-e2e:
needs:
- plan
- build-local-artifacts
uses: ./.github/workflows/ci-check-e2e.yml
with:
plan: ${{ needs.plan.outputs.val }}
secrets: inherit
# Determines if we should publish/announce
host:
needs:
- plan
- build-local-artifacts
- build-global-artifacts
- custom-build-vscode
- custom-ci-check-e2e
# Only run if we're "publishing", and only if local and global didn't fail (skipped is fine)
if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.custom-build-vscode.result == 'skipped' || needs.custom-build-vscode.result == 'success') && (needs.custom-ci-check-e2e.result == 'skipped' || needs.custom-ci-check-e2e.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
runs-on: "ubuntu-22.04"
actions: write
contents: read
name: Duplicate Actions Detection
runs-on: ubuntu-latest
outputs:
val: ${{ steps.host.outputs.manifest }}
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
cancel_others: "true"
build:
strategy:
matrix:
include:
- os: windows-latest
rust-target: x86_64-pc-windows-msvc
platform: win32
arch: x64
regular_build: 'true'
- os: windows-latest
rust-target: aarch64-pc-windows-msvc
platform: win32
arch: arm64
- os: ubuntu-20.04
rust-target: x86_64-unknown-linux-gnu
platform: linux
arch: x64
regular_build: 'true'
- os: ubuntu-20.04
rust-target: aarch64-unknown-linux-gnu
platform: linux
arch: arm64
- os: ubuntu-20.04
rust-target: arm-unknown-linux-gnueabihf
platform: linux
arch: armhf
- os: macos-11
rust-target: x86_64-apple-darwin
platform: darwin
arch: x64
- os: macos-11
rust-target: aarch64-apple-darwin
platform: darwin
arch: arm64
regular_build: 'true'
name: build (${{ matrix.platform }}-${{ matrix.arch }})
runs-on: ${{ matrix.os }}
env:
target: ${{ matrix.platform }}-${{ matrix.arch }}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@v4
if: (startsWith(github.ref, 'refs/tags/') || matrix.regular_build == 'true')
- uses: actions/checkout@v4
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
# Fetch artifacts from scratch-storage
- name: Fetch artifacts
uses: actions/download-artifact@v4
repository: Enter-tainer/typst-preview
path: external/typst-preview
ref: preview-disable-frontend
if: (startsWith(github.ref, 'refs/tags/') || matrix.regular_build == 'true')
- name: Run sccache-cache
uses: mozilla-actions/sccache-action@v0.0.4
if: (startsWith(github.ref, 'refs/tags/') || matrix.regular_build == 'true')
- name: Install Node.js
uses: actions/setup-node@v4
with:
pattern: artifacts-*
path: target/distrib/
merge-multiple: true
- id: host
shell: bash
node-version: 16
if: (startsWith(github.ref, 'refs/tags/') || matrix.regular_build == 'true')
- name: Hack typst-preview
run: |
dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json
echo "artifacts uploaded and released successfully"
cat dist-manifest.json
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
mv src/main.rs src/main.rsx
working-directory: ./external/typst-preview
if: (startsWith(github.ref, 'refs/tags/') || matrix.regular_build == 'true')
- name: Build typst-dom
run: |
yarn
yarn build
working-directory: ./external/typst-preview/addons/typst-dom
if: (startsWith(github.ref, 'refs/tags/') || matrix.regular_build == 'true')
- name: Build frontend
run: yarn
working-directory: ./external/typst-preview/addons/frontend
if: (startsWith(github.ref, 'refs/tags/') || matrix.regular_build == 'true')
- name: Build typst-preview
run: |
yarn
yarn run compile
working-directory: ./external/typst-preview/addons/vscode
if: (startsWith(github.ref, 'refs/tags/') || matrix.regular_build == 'true')
- name: Install deps
run: yarn install
if: (startsWith(github.ref, 'refs/tags/') || matrix.regular_build == 'true')
- name: Build vscode extension
run: |
yarn
yarn run compile
working-directory: ./editors/vscode
if: (startsWith(github.ref, 'refs/tags/') || matrix.regular_build == 'true')
- name: rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.rust-target }}
if: (startsWith(github.ref, 'refs/tags/') || matrix.regular_build == 'true')
- name: Install llvm
if: matrix.platform == 'linux' && (startsWith(github.ref, 'refs/tags/') || matrix.regular_build == 'true')
run: |
sudo apt-get update
sudo apt-get install llvm
- name: Install AArch64 target toolchain
if: matrix.rust-target == 'aarch64-unknown-linux-gnu' && (startsWith(github.ref, 'refs/tags/') || matrix.regular_build == 'true')
run: |
sudo apt-get update
sudo apt-get install gcc-aarch64-linux-gnu
- name: Install ARM target toolchain
if: matrix.rust-target == 'arm-unknown-linux-gnueabihf' && (startsWith(github.ref, 'refs/tags/') || matrix.regular_build == 'true')
run: |
sudo apt-get update
sudo apt-get install gcc-arm-linux-gnueabihf
- name: Build tinymist binary
shell: pwsh
run: |
cargo build --release -p tinymist --target ${{ matrix.rust-target }}
if: startsWith(github.ref, 'refs/tags/') || matrix.regular_build == 'true'
- name: Rename debug symbols for windows
if: matrix.platform == 'win32' && (startsWith(github.ref, 'refs/tags/') || matrix.regular_build == 'true')
run: |
cd target/${{ matrix.rust-target }}/release
cp tinymist.pdb tinymist-${{ env.target }}.pdb
- name: Upload split debug symbols for windows
if: matrix.platform == 'win32' && (startsWith(github.ref, 'refs/tags/') || matrix.regular_build == 'true')
uses: actions/upload-artifact@v4
with:
# Overwrite the previous copy
name: artifacts-dist-manifest
path: dist-manifest.json
# Create a GitHub Release while uploading all files to it
- name: "Download GitHub Artifacts"
uses: actions/download-artifact@v4
name: tinymist-${{ env.target }}.pdb
path: target/${{ matrix.rust-target }}/release/tinymist-${{ env.target }}.pdb
- name: Split debug symbols for linux
if: matrix.platform == 'linux' && (startsWith(github.ref, 'refs/tags/') || matrix.regular_build == 'true')
run: |
cd target/${{ matrix.rust-target }}/release
llvm-objcopy --compress-debug-sections --only-keep-debug "tinymist" "tinymist-${{ env.target }}.debug"
llvm-objcopy --strip-debug --add-gnu-debuglink="tinymist-${{ env.target }}.debug" "tinymist"
- name: Upload split debug symbols for linux
if: matrix.platform == 'linux' && (startsWith(github.ref, 'refs/tags/') || matrix.regular_build == 'true')
uses: actions/upload-artifact@v4
with:
pattern: artifacts-*
path: artifacts
merge-multiple: true
- name: Cleanup
name: tinymist-${{ env.target }}.debug
path: target/${{ matrix.rust-target }}/release/tinymist-${{ env.target }}.debug
compression-level: 0
- name: Collect debug symbols for mac
if: matrix.platform == 'darwin' && (startsWith(github.ref, 'refs/tags/') || matrix.regular_build == 'true')
run: |
# Remove the granular manifests
rm -f artifacts/*-dist-manifest.json
- name: Create GitHub Release
env:
PRERELEASE_FLAG: "${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}"
RELEASE_COMMIT: "${{ github.sha }}"
dsymutil -f "target/${{ matrix.rust-target }}/release/tinymist"
mv "target/${{ matrix.rust-target }}/release/tinymist.dwarf" "target/${{ matrix.rust-target }}/release/tinymist-${{ env.target }}.dwarf"
- name: Upload split debug symbols for mac
if: matrix.platform == 'darwin' && (startsWith(github.ref, 'refs/tags/') || matrix.regular_build == 'true')
uses: actions/upload-artifact@v4
with:
name: tinymist-${{ env.target }}.dwarf
path: target/${{ matrix.rust-target }}/release/tinymist-${{ env.target }}.dwarf
- name: Copy binary to output directory
if: (startsWith(github.ref, 'refs/tags/') || matrix.regular_build == 'true')
shell: pwsh
run: |
# If we're editing a release in place, we need to upload things ahead of time
gh release upload "${{ needs.plan.outputs.tag }}" artifacts/*
cp "target/${{ matrix.rust-target }}/release/tinymist$(If ('${{ matrix.platform }}' -eq 'win32') { '.exe' } else { '' } )" "editors/vscode/out/"
cp "target/${{ matrix.rust-target }}/release/tinymist$(If ('${{ matrix.platform }}' -eq 'win32') { '.exe' } else { '' } )" "tinymist-${{ env.target }}$(If ('${{ matrix.platform }}' -eq 'win32') { '.exe' } else { '' } )"
- name: Upload binary artifact
if: (startsWith(github.ref, 'refs/tags/') || matrix.regular_build == 'true')
uses: actions/upload-artifact@v4
with:
name: tinymist-${{ env.target }}
path: tinymist-${{ env.target }}${{ fromJSON('["", ".exe"]')[matrix.platform == 'win32'] }}
- name: Package extension
if: (startsWith(github.ref, 'refs/tags/') || matrix.regular_build == 'true')
shell: pwsh
run: yarn run package -- --target ${{ env.target }} -o tinymist-${{ env.target }}.vsix
working-directory: ./editors/vscode
- name: Upload VSIX artifact
if: (startsWith(github.ref, 'refs/tags/') || matrix.regular_build == 'true')
uses: actions/upload-artifact@v4
with:
name: tinymist-${{ env.target }}.vsix
path: editors/vscode/tinymist-${{ env.target }}.vsix
gh release edit "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --draft=false
announce:
needs:
- plan
- host
# use "always() && ..." to allow us to wait for all publish jobs while
# still allowing individual publish jobs to skip themselves (for prereleases).
# "host" however must run to completion, no skipping allowed!
if: ${{ always() && needs.host.result == 'success' }}
runs-on: "ubuntu-22.04"
build_alpine:
name: build (x86_64-unknown-linux-musl)
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/')
container:
image: rust:alpine
volumes:
- /usr/local/cargo/registry:/usr/local/cargo/registry
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
target: alpine-x64
RUST_TARGET: x86_64-unknown-linux-musl
RUSTFLAGS: "-C link-arg=-fuse-ld=lld -C target-feature=-crt-static"
steps:
- name: Install dependencies
run: apk add --no-cache git clang lld musl-dev nodejs npm yarn binutils
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Run sccache-cache
uses: mozilla-actions/sccache-action@v0.0.4
- uses: actions/checkout@v4
with:
repository: Enter-tainer/typst-preview
path: external/typst-preview
ref: preview-disable-frontend
- name: Hack typst-preview
run: |
mv src/main.rs src/main.rsx
working-directory: ./external/typst-preview
- name: Build typst-dom
run: |
yarn
yarn build
working-directory: ./external/typst-preview/addons/typst-dom
- name: Build frontend
run: yarn
working-directory: ./external/typst-preview/addons/frontend
- name: Build typst-preview
run: |
yarn
yarn run compile
working-directory: ./external/typst-preview/addons/vscode
- name: Install deps
run: yarn install
- name: Build vscode extension
run: |
yarn
yarn run compile
working-directory: ./editors/vscode
- name: Build tinymist binary
run: |
cargo build --release -p tinymist --target $RUST_TARGET
mkdir -p editors/vscode/out
- name: Split debug symbols
run: |
cd target/$RUST_TARGET/release
objcopy --compress-debug-sections --only-keep-debug "tinymist" "tinymist-${{ env.target }}.debug"
objcopy --strip-debug --add-gnu-debuglink="tinymist-${{ env.target }}.debug" "tinymist"
- name: Upload split debug symbols
uses: actions/upload-artifact@v4
with:
name: tinymist-${{ env.target }}.debug
path: target/${{ env.RUST_TARGET }}/release/tinymist-${{ env.target }}.debug
- name: Copy binary to output directory
run: |
cp "target/${{ env.RUST_TARGET }}/release/tinymist" "editors/vscode/out/"
cp "target/${{ env.RUST_TARGET }}/release/tinymist" "tinymist-${{ env.target }}"
- name: Upload binary artifact
uses: actions/upload-artifact@v4
with:
name: tinymist-${{ env.target }}
path: tinymist-${{ env.target }}
- name: Package extension
run: yarn run package -- --target ${{ env.target }} -o tinymist-${{ env.target }}.vsix
working-directory: ./editors/vscode
- name: Upload VSIX artifact
uses: actions/upload-artifact@v4
with:
name: tinymist-${{ env.target }}.vsix
path: editors/vscode/tinymist-${{ env.target }}.vsix
release:
runs-on: ubuntu-latest
needs: [build, build_alpine]
if: success() && startsWith(github.ref, 'refs/tags/')
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
submodules: recursive
- uses: actions/download-artifact@v4
with:
path: artifacts
- name: Display structure of downloaded files
run: ls -R artifacts
- uses: ncipollo/release-action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
artifacts: "artifacts/*/*"
allowUpdates: true
omitBodyDuringUpdate: true
omitDraftDuringUpdate: true
omitNameDuringUpdate: true
omitPrereleaseDuringUpdate: true
publish:
runs-on: ubuntu-latest
needs: [build]
if: success() && startsWith(github.ref, 'refs/tags/')
steps:
- uses: actions/download-artifact@v4
- name: Deploy to VS Code Marketplace
run: npx vsce publish --packagePath $(find . -type f -iname '*.vsix')
env:
VSCE_PAT: ${{ secrets.VSCODE_MARKETPLACE_TOKEN }}
- name: Deploy to OpenVSX
run: npx ovsx publish --packagePath $(find . -type f -iname '*.vsix')
env:
OVSX_PAT: ${{ secrets.OPENVSX_ACCESS_TOKEN }}

11
.gitignore vendored
View file

@ -5,11 +5,6 @@ result*
.envrc
node_modules/
/local/
editors/vscode/out/
editors/lapce/out/
/external/typst-preview
/dist
*.pdf
.vscode/*.code-workspace
refs/
/editors/vscode/out/
/editors/lapce/out/
/external/typst-preview

View file

@ -1,25 +1,10 @@
.DS_Store
.git/**
.github/**
.vscode/**
assets/**
src/**
target/**
dist/**
icons/
node_modules/
editors/vscode/test-dist/
editors/vscode/out/
editors/vscode/.vscode-test/**
*.toml
*.txt
*.lock
*.md
.env
.env.*
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

View file

@ -1,13 +0,0 @@
module.exports = {
// same options as rust-analyzer, otherwise defaults from prettier
printWidth: 100,
tabWidth: 2,
useTabs: false,
semi: true,
singleQuote: false,
quoteProps: "as-needed",
trailingComma: "all",
bracketSpacing: true,
arrowParens: "always",
singleAttributePerLine: false,
};

107
.vscode/launch.json vendored
View file

@ -15,21 +15,7 @@
"preLaunchTask": "VS Code Extension Prelaunch"
},
{
"name": "Run Extension [Web]",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentKind=web",
"--extensionDevelopmentPath=${workspaceFolder}/editors/vscode"
],
"outFiles": [
"${workspaceFolder}/editors/vscode/out/**/*.js"
],
"preLaunchTask": "VS Code Extension Prelaunch [Web]"
},
{
"name": "Run Extension [Release]",
"name": "Run Extension [Jaeger]",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
@ -39,96 +25,7 @@
"outFiles": [
"${workspaceFolder}/editors/vscode/out/**/*.js"
],
"preLaunchTask": "VS Code Extension Prelaunch [Release]"
},
{
"name": "Run Extension [Typst Preview]",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}/contrib/typst-preview/editors/vscode"
],
"outFiles": [
"${workspaceFolder}/contrib/typst-preview/editors/vscode/out/**/*.js"
],
"preLaunchTask": "VS Code Extension Prelaunch Preview"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug unit tests in executable 'tinymist'",
"cargo": {
"args": [
"test",
"--no-run",
"--bin=tinymist",
"--package=tinymist"
],
"filter": {
"name": "tinymist",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug unit tests in library 'tinymist-query'",
"cargo": {
"args": [
"test",
"--no-run",
"--lib",
"--package=tinymist-query"
],
"filter": {
"name": "tinymist-query",
"kind": "lib"
}
},
"args": [],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug unit tests in library 'tinymist-render'",
"cargo": {
"args": [
"test",
"--no-run",
"--lib",
"--package=tinymist-render"
],
"filter": {
"name": "tinymist-render",
"kind": "lib"
}
},
"args": [],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug integration test 'tinymist-e2e-tests'",
"cargo": {
"args": [
"test",
"--no-run",
"--test=tinymist-e2e-tests",
"--package=tests"
],
"filter": {
"name": "tinymist-e2e-tests",
"kind": "test"
}
},
"args": [],
"cwd": "${workspaceFolder}"
"preLaunchTask": "VS Code Extension Prelaunch [Jaeger]"
}
]
}

View file

@ -2,8 +2,5 @@
"eslint.format.enable": true,
"eslint.workingDirectories": [
"editors/vscode"
],
"tinymist.fontPaths": [
"assets/fonts"
],
]
}

137
.vscode/tasks.json vendored
View file

@ -3,30 +3,10 @@
"tasks": [
{
"label": "VS Code Extension Prelaunch",
"type": "npm",
"script": "prelaunch:vscode",
"group": "build",
},
{
"label": "VS Code Extension Prelaunch [Web]",
"type": "npm",
"script": "build:web",
"group": "build"
},
{
"label": "VS Code Extension Prelaunch Preview",
"dependsOn": [
"Compile Typst Preview Extension",
"Copy Debug LSP Binary to Typst Preview Extension"
],
"dependsOrder": "sequence",
},
{
"label": "VS Code Extension Prelaunch [Release]",
"dependsOn": [
"Compile VS Code Extension",
"Build Release LSP Binary",
"Copy Release LSP Binary to VS Code Extension"
"Build Debug LSP Binary",
"Copy Debug LSP Binary to VS Code Extension"
],
"dependsOrder": "sequence",
},
@ -36,10 +16,7 @@
"Compile VS Code Extension",
"Build Release LSP Binary",
"Copy Release LSP Binary to VS Code Extension",
"Generate VS Code Extension Bundle",
"Compile Typst Preview Extension",
"Copy Release LSP Binary to Typst Preview Extension",
"Generate Typst Preview Extension Bundle"
"Generate VS Code Extension Bundle"
],
"dependsOrder": "sequence",
},
@ -50,13 +27,6 @@
"path": "editors/vscode",
"group": "build",
},
{
"label": "Compile VS Code Extension [Web]",
"type": "npm",
"script": "compile:web",
"path": "editors/vscode",
"group": "build",
},
{
"label": "Generate VS Code Extension Bundle",
"type": "npm",
@ -65,33 +35,50 @@
"group": "build",
},
{
"label": "Compile Typst Preview Extension",
"type": "npm",
"script": "compile",
"path": "contrib/typst-preview/editors/vscode",
"group": "build",
},
{
"label": "Generate Typst Preview Extension Bundle",
"type": "npm",
"script": "package",
"path": "contrib/typst-preview/editors/vscode",
"group": "build",
"label": "Build Debug LSP Binary",
"type": "cargo",
"command": "build",
"args": [ "--bin", "tinymist" ],
"problemMatcher": [
"$rustc"
],
"group": "build"
},
{
"label": "Build Release LSP Binary",
"type": "cargo",
"command": "build",
"args": [
"--release",
"--bin",
"tinymist"
],
"args": [ "--release", "--bin", "tinymist" ],
"problemMatcher": [
"$rustc"
],
"group": "build"
},
{
"label": "Copy Debug LSP Binary to VS Code Extension",
"type": "shell",
"windows": {
"command": "cp",
"args": [
"${workspaceFolder}\\target\\debug\\tinymist.exe",
"${workspaceFolder}\\editors\\vscode\\out\\"
]
},
"linux": {
"command": "cp",
"args": [
"${workspaceFolder}/target/debug/tinymist",
"${workspaceFolder}/editors/vscode/out/"
]
},
"osx": {
"command": "cp",
"args": [
"${workspaceFolder}/target/debug/tinymist",
"${workspaceFolder}/editors/vscode/out/"
]
}
},
{
"label": "Copy Release LSP Binary to VS Code Extension",
"type": "shell",
@ -116,56 +103,6 @@
"${workspaceFolder}/editors/vscode/out/"
]
}
},
{
"label": "Copy Debug LSP Binary to Typst Preview Extension",
"type": "shell",
"windows": {
"command": "cp",
"args": [
"${workspaceFolder}\\target\\debug\\tinymist.exe",
"${workspaceFolder}\\contrib\\typst-preview\\editors\\vscode\\out\\"
]
},
"linux": {
"command": "cp",
"args": [
"${workspaceFolder}/target/debug/tinymist",
"${workspaceFolder}/contrib/typst-preview/editors/vscode/out/"
]
},
"osx": {
"command": "cp",
"args": [
"${workspaceFolder}/target/debug/tinymist",
"${workspaceFolder}/contrib/typst-preview/editors/vscode/out/"
]
}
},
{
"label": "Copy Release LSP Binary to Typst Preview Extension",
"type": "shell",
"windows": {
"command": "cp",
"args": [
"${workspaceFolder}\\target\\release\\tinymist.exe",
"${workspaceFolder}\\contrib\\typst-preview\\editors\\vscode\\out\\"
]
},
"linux": {
"command": "cp",
"args": [
"${workspaceFolder}/target/release/tinymist",
"${workspaceFolder}/contrib/typst-preview/editors/vscode/out/"
]
},
"osx": {
"command": "cp",
"args": [
"${workspaceFolder}/target/release/tinymist",
"${workspaceFolder}/contrib/typst-preview/editors/vscode/out/"
]
}
}
]
}

View file

@ -1,37 +0,0 @@
{
"$schema": "vscode://schemas/workspaceConfig",
"folders": [
{
"path": ".."
}
],
"settings": {
"tinymist.fontPaths": [
"assets/fonts"
],
"[javascript]":{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
},
"[json]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"eslint.format.enable": true,
"eslint.workingDirectories": [
"editors/vscode"
],
"rust-analyzer.check.command": "clippy",
"rust-analyzer.rustfmt.extraArgs": ["--config=wrap_comments=true"],
"files.watcherExclude": {
"**/target": true
},
},
"extensions": {
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"myriad-dreamin.tinymist",
]
},
}

View file

@ -1,22 +0,0 @@
// Folder-specific settings
//
// For a full list of overridable settings, and general information on folder-specific settings,
// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
{
"lsp": {
"rust-analyzer": {
"initialization_options": {
"check": {
"command": "clippy"
}
}
},
"tinymist": {
"initialization_options": {
"exportPdf": "onSave",
"fontPaths": ["assets/fonts"],
"outputPath": "$root/target/typst/$dir/$name"
}
}
}
}

View file

@ -1 +0,0 @@
[CHANGELOG.md](./editors/vscode/CHANGELOG.md)

File diff suppressed because it is too large Load diff

View file

@ -1,4 +0,0 @@
# CHANGELOGs
- [CHANGELOG-2025.md](/editors/vscode/CHANGELOG.md)
- [CHANGELOG-2024.md](./CHANGELOG-2024.md)

View file

@ -1,2 +0,0 @@
# Only Myriad-Dreamin and Enter-tainer can merge changes to this repository.
* @Myriad-Dreamin @Enter-tainer

View file

@ -1,22 +0,0 @@
# Contributing
Thank you for your interest in contributing to tinymist! There are many ways to contribute and we appreciate all of them.
Before contributing, you can read the [Development Guide](./docs/dev-guide.md) to learn more about the project structure and how to build and run the project.
## Fixing a bug or improving a feature
Generally it's fine to just work on these kinds of things and put a pull-request out for it. If there is an issue accompanying it make sure to link it in the pull request description so it can be closed afterwards or linked for context.
If you want to find something to fix or work on keep a look out for the [`bug`](https://github.com/Myriad-Dreamin/tinymist/issues?q=is%3Aissue+is%3Aopen+label%3Abug) and [`enhancement`](https://github.com/Myriad-Dreamin/tinymist/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement)
labels. Among issues, the [`good first issue`](https://github.com/Myriad-Dreamin/tinymist/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) label is a good place to start.
## Localization
Tinymist is localized in multiple languages. You can contribute to the localization of Tinymist by translating or correcting the locale files in the [locales](./locales) folder. This is also a good start of contributing to the project.
## Implementing a new feature
It's advised to first open an issue for any kind of new feature so the team can tell upfront whether the feature is desirable or not before any implementation work happens. We want to minimize the possibility of someone putting a lot of work into a feature that is then going to waste as we deem it out of scope (be it due to generally not fitting in with tinymist, or just not having the maintenance capacity). If there already is a feature issue open but it is not clear whether it is considered accepted feel free to just drop a comment and ask!

4966
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,220 +1,80 @@
[workspace.package]
description = "An integrated language service for Typst."
authors = ["Myriad-Dreamin <camiyoru@gmail.com>", "Nathan Varner"]
version = "0.14.6-rc1"
edition = "2024"
version = "0.10.3"
edition = "2021"
readme = "README.md"
license = "Apache-2.0"
homepage = "https://github.com/Myriad-Dreamin/tinymist"
repository = "https://github.com/Myriad-Dreamin/tinymist"
# also change in ci.yml
rust-version = "1.89"
rust-version = "1.74"
[workspace]
resolver = "2"
members = ["benches/*", "crates/*", "tests"]
members = ["crates/*"]
[workspace.dependencies]
# Basic Infra
once_cell = "1"
anyhow = "1"
itertools = "0.13"
paste = "1.0"
cfg-if = "1.0"
strum = { version = "0.27.2", features = ["derive"] }
quote = "1"
syn = "2"
triomphe = { version = "0.1.10", default-features = false, features = ["std"] }
# Asynchoronous and Multi-threading
async-trait = "0.1.89"
fxhash = "0.2.1"
ecow = "0.2.1"
comemo = "0.4"
ena = "0.14.2"
futures = "0.3"
rayon = "1.11.0"
tokio = { version = "1.48.0", features = ["macros"] }
tokio-util = { version = "0.7.16", features = ["compat"] }
# System
battery = "0.7.8"
temp-env = "0.3.6"
open = { version = "5.3.2" }
regex = "1.10.3"
itertools = "0.12.1"
lazy_static = "1.4.0"
env_logger = "0.11.3"
log = "0.4.21"
strum = { version = "0.26.2", features = ["derive"] }
async-trait = "0.1.77"
parking_lot = "0.12.1"
walkdir = "2"
chrono = { version = "0.4", default-features = false }
time = "0.3"
dirs = "6"
fontdb = { version = "0.23", default-features = false }
notify = "6"
path-clean = "1.0.1"
windows-sys = "0.61.2"
tempfile = "3.19.1"
same-file = "1.0.6"
libc = "0.2.155"
core-foundation = { version = "0.10.0", features = ["mac_os_10_7_support"] }
half = "=2.6.0"
# Web
js-sys = "^0.3"
wasm-bindgen = "^0.2"
wasm-bindgen-futures = "^0.4"
wasm-bindgen-test = "0.3.45"
web-sys = "^0.3"
web-time = { version = "1.1.0" }
console_error_panic_hook = { version = "0.1.7" }
# Networking
hyper = { version = "1", features = ["full"] }
hyper-util = { version = "0.1.17", features = ["tokio"] }
hyper-tungstenite = "0.19.0"
reqwest = { version = "^0.12", default-features = false, features = [
"rustls-tls",
"blocking",
"multipart",
] }
http-body-util = "0.1.3"
# Algorithms
base64 = "0.22"
regex = "1.12.2"
# Cryptography and data processing
rustc-hash = { version = "2", features = ["std"] }
siphasher = "1"
fxhash = "0.2.1"
sha2 = "0.10.9"
nohash-hasher = "0.2.0"
fastrand = "2.3.0"
# Data Structures
bitvec = "1"
comemo = "0.5.0"
# We need to freeze the version of the crate, as the raw-api feature is considered unstable
dashmap = { version = "=5.5.3", features = ["raw-api"] }
ecow = "0.2.6"
ena = "0.14.3"
hashbrown = { version = "0.14", features = [
"inline-more",
], default-features = false }
indexmap = "2.12.0"
rpds = "1"
# Data/Text Format and Processing
biblatex = "0.11"
bytes = "1"
cmark-writer = { version = "0.9.0", features = [
"gfm",
], path = "crates/cmark-writer" }
docx-rs = { version = "0.4.18-rc19", git = "https://github.com/Myriad-Dreamin/docx-rs", default-features = false, rev = "db49a729f68dbdb9e8e91857fbb1c3d414209871" }
hayagriva = "0.9.1"
hex = "0.4.3"
flate2 = "1"
# typst can only support these formats.
image = { version = "0.25.5", default-features = false, features = [
"png",
"jpeg",
"gif",
] }
pathdiff = "0.2"
percent-encoding = "2"
rust_iso639 = "0.0.3"
rust_iso3166 = "0.1.4"
resvg = { version = "0.45" }
rkyv = "0.7.42"
semver = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_repr = "0.1"
serde_with = { version = "3.6", features = ["base64"] }
serde_yaml = "0.9"
serde-wasm-bindgen = "^0.6"
tar = "0.4"
indexmap = "2.1.0"
paste = "1.0"
toml = { version = "0.8", default-features = false, features = [
"parse",
"display",
] }
ttf-parser = "0.25.0"
unicode-script = "0.5"
unscanny = "0.1"
yaml-rust2 = "0.9"
# Logging
codespan-reporting = "0.11"
env_logger = "0.11.3"
log = "0.4"
# Typst
reflexo = { version = "=0.7.0-rc2", default-features = false, features = [
"flat-vector",
] }
reflexo-typst = { version = "=0.7.0-rc2", default-features = false }
reflexo-vec2svg = { version = "=0.7.0-rc2" }
typst = "0.11.0"
typst-ide = "0.11.0"
typst-pdf = "0.11.0"
typst-assets = { git = "https://github.com/typst/typst-assets", rev = "4d1211a" }
typst-ts-core = { version = "0.4.2-rc8" }
typst-ts-compiler = { version = "0.4.2-rc8" }
typst-preview = { git = "https://github.com/Enter-tainer/typst-preview", rev = "18630ebda22339109ef675a885787f4fc8731ba8" }
typst = "0.14.2"
typst-html = "0.14.2"
typst-library = "0.14.2"
typst-macros = "0.14.2"
typst-timing = "0.14.2"
typst-svg = "0.14.2"
typst-render = "0.14.2"
typst-pdf = "0.14.2"
typst-syntax = "0.14.2"
typst-eval = "0.14.2"
typst-assets = "0.14.2"
typstfmt = { version = "0", git = "https://github.com/Myriad-Dreamin/typstfmt", tag = "v0.13.1" }
typst-ansi-hl = "0.4.0"
typstyle-core = { version = "=0.14.0", default-features = false }
# LSP
crossbeam-channel = "0.5.15"
lsp-server = "0.7.6"
lsp-types = { version = "=0.95.0", features = ["proposed"] }
dapts = "0.0.6"
crossbeam-channel = "0.5.12"
# CLI
clap = { version = "4.5", features = ["derive", "env", "unicode"] }
clap_builder = { version = "4.5", features = ["string"] }
clap_complete = "4.5"
clap_complete_fig = "4.5"
clap_complete_nushell = "4.5.3"
clap_mangen = { version = "0.2.22" }
vergen = { version = "8.3.1", features = [
clap = { version = "4.4", features = ["derive", "env", "unicode", "wrap_help"] }
clap_builder = { version = "4", features = ["string"] }
clap_complete = "4.4"
clap_complete_fig = "4.4"
clap_mangen = { version = "0.2.15" }
vergen = { version = "8.2.5", features = [
"build",
"cargo",
"git",
"gitcl",
"rustc",
] }
tokio = { version = "1.36.0", features = [
"macros",
"rt-multi-thread",
"io-std",
] }
tokio-util = "0.7.10"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# Testing
dhat = "0.3.3"
divan = "0.1.21"
insta = { version = "1.43", features = ["glob", "filters"] }
insta-cmd = "0.6.0"
# Our Own Crates
tinymist-assets = { version = "=0.14.6-rc1" }
tinymist-derive = { path = "./crates/tinymist-derive/", version = "0.14.6-rc2" }
tinymist-l10n = { path = "./crates/tinymist-l10n/", version = "0.14.6-rc2" }
tinymist-package = { path = "./crates/tinymist-package/", version = "0.14.6-rc2" }
tinymist-std = { path = "./crates/tinymist-std/", version = "0.14.6-rc2", default-features = false }
tinymist-vfs = { path = "./crates/tinymist-vfs/", version = "0.14.6-rc2", default-features = false }
tinymist-world = { path = "./crates/tinymist-world/", version = "0.14.6-rc2", default-features = false }
tinymist-project = { path = "./crates/tinymist-project/", version = "0.14.6-rc2" }
tinymist-task = { path = "./crates/tinymist-task/", version = "0.14.6-rc2" }
typst-shim = { path = "./crates/typst-shim", version = "0.14.6-rc2" }
tinymist-tests = { path = "./crates/tinymist-tests/" }
sync-ls = { path = "./crates/sync-lsp", version = "0.14.6-rc1" }
tinymist = { path = "./crates/tinymist/", version = "0.14.6-rc1", default-features = false }
tinymist-analysis = { path = "./crates/tinymist-analysis/", version = "0.14.6-rc1" }
tinymist-cli = { path = "./crates/tinymist-cli/", version = "0.14.6-rc1" }
tinymist-debug = { path = "./crates/tinymist-debug/", version = "0.14.6-rc1" }
tinymist-lint = { path = "./crates/tinymist-lint/", version = "0.14.6-rc1" }
tinymist-query = { path = "./crates/tinymist-query/", version = "0.14.6-rc1" }
tinymist-render = { path = "./crates/tinymist-render/", version = "0.14.6-rc1" }
tinymist-preview = { path = "./crates/typst-preview", version = "0.14.6-rc1" }
typlite = { path = "./crates/typlite", version = "0.14.6-rc1", default-features = false }
divan = "0.1.14"
insta = { vesrion = "1.36", features = ["glob"] }
[profile.dev.package.insta]
opt-level = 3
@ -223,115 +83,43 @@ opt-level = 3
opt-level = 3
[profile.release]
# lto = true # Enable link-time optimization
codegen-units = 1 # Reduce number of codegen units to increase optimizations
panic = "abort" # Abort on panic
[profile.gh-release]
inherits = "release"
lto = true # Enable link-time optimization
debug = true
# The profile that 'dist' will build with
[profile.dist]
inherits = "release"
lto = "thin"
[workspace.lints.rustdoc]
broken_intra_doc_links = "warn"
opt-level = 3 # Optimize for speed
codegen-units = 1 # Reduce number of codegen units to increase optimizations
panic = 'abort' # Abort on panic
[workspace.lints.rust]
missing_docs = "warn"
# missing_crate_level_docs = "warn"
unexpected_cfgs = { level = "allow", check-cfg = [
'cfg(wasm_bindgen_unstable_test_coverage)',
'cfg(noop)',
'cfg(used_linker)',
] }
[workspace.lints.clippy]
uninlined_format_args = "warn"
# missing_errors_doc = "warn"
# missing_panics_doc = "warn"
# missing_docs_in_private_items = "warn"
missing_safety_doc = "warn"
undocumented_unsafe_blocks = "warn"
[workspace.metadata.typos.default]
locale = "en-us"
[workspace.metadata.typos.default.extend-words]
labelled = "labelled"
onces = "onces"
withs = "withs"
[workspace.metadata.typos.files]
ignore-hidden = false
extend-exclude = ["/.git", "fixtures"]
[patch.crates-io]
# This patch is used to bundle a locally built frontend (HTML) of `typst-preview`.
# The shortcoming is that you cannot install tinymist from git source then, i.e. `cargo install --git ..`, with this patch.
# However, it is not suggested to install it in that way. The suggested ways are:
# - Installation: https://github.com/Myriad-Dreamin/tinymist?tab=readme-ov-file#installation
# - Installing pre-built artifacts from GitHub : https://github.com/Myriad-Dreamin/tinymist?tab=readme-ov-file#installing-regularnightly-prebuilds-from-github
# tinymist-assets = { path = "./crates/tinymist-assets/" }
# These patches use a different version of `typst`, which only exports some private functions and information for code analysis.
#
# A regular build MUST use `tag` or `rev` to specify the version of the patched crate to ensure stability.
typst = { git = "https://github.com/Myriad-Dreamin/typst.git", tag = "tinymist/v0.14.6-rc2" }
typst-macros = { git = "https://github.com/Myriad-Dreamin/typst.git", tag = "tinymist/v0.14.6-rc2" }
typst-library = { git = "https://github.com/Myriad-Dreamin/typst.git", tag = "tinymist/v0.14.6-rc2" }
typst-html = { git = "https://github.com/Myriad-Dreamin/typst.git", tag = "tinymist/v0.14.6-rc2" }
typst-timing = { git = "https://github.com/Myriad-Dreamin/typst.git", tag = "tinymist/v0.14.6-rc2" }
typst-svg = { git = "https://github.com/Myriad-Dreamin/typst.git", tag = "tinymist/v0.14.6-rc2" }
typst-render = { git = "https://github.com/Myriad-Dreamin/typst.git", tag = "tinymist/v0.14.6-rc2" }
typst-pdf = { git = "https://github.com/Myriad-Dreamin/typst.git", tag = "tinymist/v0.14.6-rc2" }
typst-syntax = { git = "https://github.com/Myriad-Dreamin/typst.git", tag = "tinymist/v0.14.6-rc2" }
typst-eval = { git = "https://github.com/Myriad-Dreamin/typst.git", tag = "tinymist/v0.14.6-rc2" }
typst = { git = "https://github.com/Myriad-Dreamin/typst.git", branch = "tinymist-v0.11.0" }
typst-ide = { git = "https://github.com/Myriad-Dreamin/typst.git", branch = "tinymist-v0.11.0" }
typst-pdf = { git = "https://github.com/Myriad-Dreamin/typst.git", branch = "tinymist-v0.11.0" }
typst-syntax = { git = "https://github.com/Myriad-Dreamin/typst.git", branch = "tinymist-v0.11.0" }
# typst-ansi-hl = { git = "https://github.com/ParaN3xus/typst-ansi-hl.git", branch = "nightly" }
# typstyle-core = { git = "https://github.com/ParaN3xus/typstyle/", rev = "34869bfa9db089c5196be5fac2a5e9d752904ca2" }
# These patches use local `typst` for development.
# typst = { path = "../typst/crates/typst" }
# typst-timing = { path = "../typst/crates/typst-timing" }
# typst-svg = { path = "../typst/crates/typst-svg" }
# typst-ide = { path = "../typst/crates/typst-ide" }
# typst-pdf = { path = "../typst/crates/typst-pdf" }
# typst-render = { path = "../typst/crates/typst-render" }
# typst-syntax = { path = "../typst/crates/typst-syntax" }
# These patches use local `typstyle-core` for development.
# typstyle-core = { path = "../typstyle/crates/typstyle-core" }
typst-ts-svg-exporter = { git = "https://github.com/Myriad-Dreamin/typst.ts", rev = "2fc877de0a4bcd7a8f057933546a97348e9621c7", package = "typst-ts-svg-exporter" }
typst-ts-core = { git = "https://github.com/Myriad-Dreamin/typst.ts", rev = "2fc877de0a4bcd7a8f057933546a97348e9621c7", package = "typst-ts-core" }
typst-ts-compiler = { git = "https://github.com/Myriad-Dreamin/typst.ts", rev = "2fc877de0a4bcd7a8f057933546a97348e9621c7", package = "typst-ts-compiler" }
# These patches use a different version of `reflexo`.
#
# A regular build MUST use `tag` or `rev` to specify the version of the patched crate to ensure stability.
reflexo = { git = "https://github.com/Myriad-Dreamin/typst.ts/", rev = "c078ddf869d9438b36e1cacb65100e4514780dc1" }
reflexo-typst = { git = "https://github.com/Myriad-Dreamin/typst.ts/", rev = "c078ddf869d9438b36e1cacb65100e4514780dc1" }
reflexo-vec2svg = { git = "https://github.com/Myriad-Dreamin/typst.ts/", rev = "c078ddf869d9438b36e1cacb65100e4514780dc1" }
# typst-ts-svg-exporter = { path = "../typst.ts/exporter/svg" }
# typst-ts-core = { path = "../typst.ts/core" }
# typst-ts-compiler = { path = "../typst.ts/compiler" }
# These patches use local `reflexo` for development.
# reflexo = { path = "../typst.ts/crates/reflexo/" }
# reflexo-typst = { path = "../typst.ts/crates/reflexo-typst/" }
# reflexo-vec2svg = { path = "../typst.ts/crates/conversion/vec2svg/" }
typst-shim = { path = "crates/typst-shim" }
tinymist-analysis = { path = "crates/tinymist-analysis" }
tinymist-std = { path = "crates/tinymist-std" }
tinymist-vfs = { path = "crates/tinymist-vfs" }
tinymist-world = { path = "crates/tinymist-world" }
tinymist-project = { path = "crates/tinymist-project" }
tinymist-task = { path = "crates/tinymist-task" }
# If reflexo use the tinymist from git, you should use the following patch.
# [patch."https://github.com/ParaN3xus/tinymist.git"]
# typst-shim = { path = "crates/typst-shim" }
# tinymist-analysis = { path = "crates/tinymist-analysis" }
# tinymist-std = { path = "crates/tinymist-std" }
# tinymist-vfs = { path = "crates/tinymist-vfs" }
# tinymist-world = { path = "crates/tinymist-world" }
# tinymist-project = { path = "crates/tinymist-project" }
# tinymist-task = { path = "crates/tinymist-task" }
# https://github.com/rust-lang/cargo/issues/8690
[patch."https://github.com/Enter-tainer/typst-preview"]
typst-preview = { path = "external/typst-preview" }

View file

@ -1,48 +0,0 @@
# ```shell
# docker build -t myriaddreamin/tinymist:latest .
# ```
#
# ## References
#
# https://stackoverflow.com/questions/58473606/cache-rust-dependencies-with-docker-build
# https://stackoverflow.com/a/64528456
# https://depot.dev/blog/rust-dockerfile-best-practices
ARG RUST_VERSION=1.89.0
FROM rust:${RUST_VERSION}-bookworm AS base
RUN apt-get install -y git
RUN cargo install sccache --version ^0.7
RUN cargo install cargo-chef --version ^0.1
ENV RUSTC_WRAPPER=sccache SCCACHE_DIR=/sccache
# to download the toolchain
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=$SCCACHE_DIR,sharing=locked \
rustup update
FROM base as planner
WORKDIR app
# We only pay the installation cost once,
# it will be cached from the second build onwards
RUN cargo install cargo-chef
COPY . .
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=$SCCACHE_DIR,sharing=locked \
cargo +${RUST_VERSION} chef prepare --recipe-path recipe.json
FROM base as builder
WORKDIR app
RUN cargo install cargo-chef
COPY --from=planner /app/recipe.json recipe.json
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=$SCCACHE_DIR,sharing=locked \
cargo +${RUST_VERSION} chef cook --release --recipe-path recipe.json
COPY . .
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=$SCCACHE_DIR,sharing=locked \
cargo +${RUST_VERSION} build --bin tinymist --release
FROM debian:12
WORKDIR /app/
COPY --from=builder /app/target/release/tinymist /usr/local/bin
ENTRYPOINT ["/usr/local/bin/tinymist"]

View file

@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2023-2025 Myriad Dreamin, Nathan Varner
Copyright 2023 Myriad Dreamin, Nathan Varner
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View file

@ -1,56 +0,0 @@
<!-- This file is generated by scripts/link-docs.mjs. Do not edit manually. -->
# Tinymist Maintainers
Tinymist [ˈtaɪni mɪst] is an integrated language service for [Typst](https://typst.app/) [taɪpst].
This page is generated from [./MAINTAINERS.typ](./MAINTAINERS.typ) and renders information of [maintainers](#maintainers) and [features.](#features)
## Maintainers
- [**Myriad-Dreamin**](https://github.com/Myriad-Dreamin)
- Email: camiyoru@gmail.com
- Maintains: *[Editor Integration](#editor-integration)*, *[Language Service](#language-service)*, *[Document Previewing](#document-previewing)*, *[VS Code Client-Side Support](#vs-code-client-side-support)*, and *[Nightly Releases](#nightly-releases)*
- [**Enter-tainer**](https://github.com/Enter-tainer)
- Email: mgt@oi-wiki.org
- Maintains: *[Editor Integration](#editor-integration)*, *[Language Service](#language-service)*, *[Document Previewing](#document-previewing)*, and *[VS Code Client-Side Support](#vs-code-client-side-support)*
- [**ParaN3xus**](https://github.com/ParaN3xus)
- Email: paran3xus007@gmail.com
- Maintains: *[Nightly Releases](#nightly-releases)*
- [**Max397**](https://github.com/max397574)
- Email: undefined
- Maintains: *[Editor Integration](#editor-integration)*
- [**Ericoolen**](https://github.com/Eric-Song-Nop)
- Email: EricYFSong@gmail.com
- Maintains: *[Language Service](#language-service)*
- [**Caleb Maclennan**](https://github.com/alerque)
- Email: caleb@alerque.com
- Maintains: *[Editor Integration](#editor-integration)*
- [**SylvanFranklin**](https://github.com/SylvanFranklin)
- Email: sylvanfranklin@icloud.com
- Maintains: *[Editor Integration](#editor-integration)*, and *[Document Previewing](#document-previewing)*
## Features
### Editor Integration
Integrate tinymist server with popular editors like VS Code, Neovim, etc.
- Scope: [`crates/tinymist/`](./crates/tinymist/), [`editors/`](./editors/)
### Language Service
Perform code analysis and provide language support for Typst.
- Scope: [`crates/tinymist/`](./crates/tinymist/), [`crates/tinymist-analysis/`](./crates/tinymist-analysis/), [`crates/tinymist-query/`](./crates/tinymist-query/)
### Document Previewing
Provide instant preview of the document being edited.
- Scope: [`crates/tinymist/`](./crates/tinymist/), [`crates/typst-preview/`](./crates/typst-preview/), [`contrib/typst-preview/`](./contrib/typst-preview/), [`tools/typst-dom/`](./tools/typst-dom/), [`tools/typst-preview-frontend/`](./tools/typst-preview-frontend/)
### VS Code Client-Side Support
Enrich the VS Code features with the client-side extension.
- Scope: [`crates/tinymist/`](./crates/tinymist/), [`editors/vscode/`](./editors/vscode/), [`tools/editor-tools/`](./tools/editor-tools/)
### Nightly Releases
Build and Publish nightly releases of tinymist. The nightly releases are built upon the main branches of both tinymist and typst.
- Scope: [`crates/tinymist/`](./crates/tinymist/), [`crates/typst-shim/`](./crates/typst-shim/)

View file

@ -1,99 +0,0 @@
#import "typ/templates/maintainer.typ": *
#show: main
#let editor-integration = [Editor Integration]
#let language-service = [Language Service]
#let document-previewing = [Document Previewing]
#let vs-code-client-side-support = [VS Code Client-Side Support]
#let nightly-releases = [Nightly Releases]
== Maintainers
#maintainers[
- Myriad-Dreamin
- #github("Myriad-Dreamin")
- #email("camiyoru@gmail.com")
- #maintains[
- #editor-integration
- #language-service
- #document-previewing
- #vs-code-client-side-support
- #nightly-releases
]
- Enter-tainer
- #github("Enter-tainer")
- #email("mgt@oi-wiki.org")
- #maintains[
- #editor-integration
- #language-service
- #document-previewing
- #vs-code-client-side-support
]
- ParaN3xus
- #github("ParaN3xus")
- #email("paran3xus007@gmail.com")
- #maintains[
- #nightly-releases
]
- Max397
- #github("max397574")
- #maintains[
- #editor-integration
]
- Ericoolen
- #github("Eric-Song-Nop")
- #email("EricYFSong@gmail.com")
- #maintains[
- #language-service
]
- Caleb Maclennan
- #github("alerque")
- #email("caleb@alerque.com")
- #maintains[
- #editor-integration
]
- SylvanFranklin
- #github("SylvanFranklin")
- #email("sylvanfranklin@icloud.com")
- #maintains[
- #editor-integration
- #document-previewing
]
]
== Features
#features[
- #editor-integration
- #scope("crates/tinymist/", "editors/")
- #description[
Integrate tinymist server with popular editors like VS Code, Neovim, etc.
]
- #language-service
- #scope("crates/tinymist/", "crates/tinymist-analysis/", "crates/tinymist-query/")
- #description[
Perform code analysis and provide language support for Typst.
]
- #document-previewing
- #scope(
"crates/tinymist/",
"crates/typst-preview/",
"contrib/typst-preview/",
"tools/typst-dom/",
"tools/typst-preview-frontend/",
)
- #description[
Provide instant preview of the document being edited.
]
- #vs-code-client-side-support
- #scope("crates/tinymist/", "editors/vscode/", "tools/editor-tools/")
- #description[
Enrich the VS Code features with the client-side extension.
]
- #nightly-releases
- #scope("crates/tinymist/", "crates/typst-shim/")
- #description[
Build and Publish nightly releases of tinymist. The nightly releases are built upon the main branches of both tinymist and typst.
]
]

211
README.md
View file

@ -1,228 +1,53 @@
<!-- This file is generated by scripts/link-docs.mjs. Do not edit manually. -->
# Tinymist
[<img src="https://img.shields.io/github/license/Myriad-Dreamin/tinymist" alt="GitHub license" />](https://github.com/Myriad-Dreamin/tinymist/blob/main/LICENSE)[<img src="https://github.com/Myriad-Dreamin/tinymist/actions/workflows/ci.yml/badge.svg?event=push" alt="Actions status" />](https://github.com/Myriad-Dreamin/tinymist/actions/workflows/ci.yml)[<img src="https://img.shields.io/badge/view-documentation-blue" alt="Documentation" />](https://myriad-dreamin.github.io/tinymist/)[<img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki" />](https://deepwiki.com/Myriad-Dreamin/tinymist)
Tinymist \[ˈtaɪni mɪst\] is an integrated language service for [Typst](https://typst.app/) \[taɪpst\]. You can also call it <ruby>
<rt>
wēi
</rt>
</ruby><ruby>
<rt>
ǎi
</rt>
</ruby> in Chinese.
Tinymist [ˈtaɪni mɪst] is an integrated language service for [Typst](https://typst.app/) [taɪpst]. You can also call it "微霭" [wēi ǎi] in Chinese.
It contains:
- an analyzing library for Typst, see [tinymist-query](/crates/tinymist-query/).
- a CLI for Typst, see [tinymist](/crates/tinymist/).
- which provides a language server for Typst, see [Language Features](https://myriad-dreamin.github.io/tinymist/feature/language.html).
- which provides a preview server for Typst, see [Preview Feature](https://myriad-dreamin.github.io/tinymist/feature/preview.html).
- a VSCode extension for Typst, see [Tinymist VSCode Extension](/editors/vscode/).
- an analyzing library for Typst, see [tinymist-query](./crates/tinymist-query/).
- a CLI for Typst, see [tinymist](./crates/tinymist/).
- which provides a language server for Typst.
- a VSCode extension for Typst, see [Tinymist VSCode Extension](./editors/vscode/).
## Features
Language service (LSP) features:
- [Semantic highlighting](https://code.visualstudio.com/api/language-extensions/semantic-highlight-guide)
- The “semantic highlighting” is supplementary to [“syntax highlighting”](https://code.visualstudio.com/api/language-extensions/syntax-highlight-guide).
- [Code actions](https://code.visualstudio.com/api/language-extensions/programmatic-language-features#provide-code-actions)
- Also known as “quick fixes” or “refactorings”.
- [Formatting (Reformatting)](https://code.visualstudio.com/api/language-extensions/programmatic-language-features#format-source-code-in-an-editor)
- Provide the user with support for formatting whole documents, using [typstfmt](https://github.com/astrale-sharp/typstfmt) or [typstyle](https://github.com/Enter-tainer/typstyle).
- [Document highlight](https://code.visualstudio.com/api/language-extensions/programmatic-language-features#highlight-all-occurrences-of-a-symbol-in-a-document)
- Highlight all break points in a loop context.
- (Todo) Highlight all exit points in a function context.
- (Todo) Highlight all captures in a closure context.
- (Todo) Highlight all occurrences of a symbol in a document.
- [Document links](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentLink)
- Renders path or link references in the document, such as `image("path.png")` or `bibliography(style: "path.csl")`.
- Also known as "syntax highlighting".
- [Diagnostics](https://code.visualstudio.com/api/language-extensions/programmatic-language-features#provide-diagnostics)
- Also known as "error checking" or "error reporting".
- [Document symbols](https://code.visualstudio.com/docs/getstarted/userinterface#_outline-view)
- Also known as “document outline” or “table of contents” _in Typst_.
- Also known as "document outline" or "table of contents" **in Typst**.
- [Folding ranges](https://burkeholland.gitbook.io/vs-code-can-do-that/exercise-3-navigation-and-refactoring/folding-sections)
- You can collapse code/content blocks and headings.
- [Goto definitions](https://code.visualstudio.com/api/language-extensions/programmatic-language-features#show-definitions-of-a-symbol)
- Right-click on a symbol and select “Go to Definition”.
- Right-click on a symbol and select "Go to Definition".
- Or ctrl+click on a symbol.
- [References](https://code.visualstudio.com/api/language-extensions/programmatic-language-features#find-all-references-to-a-symbol)
- Right-click on a symbol and select “Go to References” or “Find References”.
- Right-click on a symbol and select "Go to References" or "Find References".
- Or ctrl+click on a symbol.
- [Hover tips](https://code.visualstudio.com/api/language-extensions/programmatic-language-features#show-hovers)
- Also known as “hovering tooltip”.
- Render docs according to [tidy](https://github.com/Mc-Zen/tidy) style.
- Also known as "hovering tooltip".
- [Inlay hints](https://www.jetbrains.com/help/idea/inlay-hints.html)
- Inlay hints are special markers that appear in the editor and provide you with additional information about your code, like the names of the parameters that a called method expects.
- [Color Provider](https://code.visualstudio.com/api/language-extensions/programmatic-language-features#show-color-decorators)
- View all inlay colorful label for color literals in your document.
- Change the color literals value by a color picker or its code presentation.
- [Code Lens](https://code.visualstudio.com/blogs/2017/02/12/code-lens-roundup)
- Should give contextual buttons along with code. For example, a button for exporting your document to various formats at the start of the document.
- [Rename symbols and embedded paths](https://code.visualstudio.com/api/language-extensions/programmatic-language-features#rename-symbols)
- [Rename symbols](https://code.visualstudio.com/api/language-extensions/programmatic-language-features#rename-symbols)
- [Help with function and method signatures](https://code.visualstudio.com/api/language-extensions/programmatic-language-features#help-with-function-and-method-signatures)
- [Workspace Symbols](https://code.visualstudio.com/api/language-extensions/programmatic-language-features#show-all-symbol-definitions-in-folder)
- [Code Action](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-code-actions)
- Increasing/Decreasing heading levels.
- Turn equation into “inline”, “block” or “multiple-line block” styles.
- [experimental/onEnter](https://github.com/rust-lang/rust-analyzer/blob/master/docs/dev/lsp-extensions.md#on-enter)
- <kbd>
Enter
</kbd> inside triple-slash comments automatically inserts `///`
- <kbd>
Enter
</kbd> in the middle or after a trailing space in `//` inserts `//`
- <kbd>
Enter
</kbd> inside `//!` doc comments automatically inserts `//!`
- <kbd>
Enter
</kbd> inside equation markups automatically inserts indents.
Extra features:
- Compiles to PDF on save (configurable to as-you-type, or other options). Check [Docs: Exporting Documents](https://myriad-dreamin.github.io/tinymist/feature/export.html).
- Also compiles to SVG, PNG, HTML, Markdown, Text, and other formats by commands, vscode tasks, or code lenses.
- Provides test, benchmark, coverage collecting on documents and modules. Check [Docs: Testing Features](https://myriad-dreamin.github.io/tinymist/feature/testing.html).
- Provides builtin linting. Check [Docs: Linting Features](https://myriad-dreamin.github.io/tinymist/feature/linting.html).
- Provides a status bar item to show the current documents compilation status and words count.
- [Editor tools](/tools/editor-tools/):
- View a list of templates in template gallery. (`tinymist.showTemplateGallery`)
- Click a button in template gallery to initialize a new project with a template. (`tinymist.initTemplate` and `tinymist.initTemplateInPlace`)
- Trace execution in current document (`tinymist.profileCurrentFile`).
## Versioning and Release Cycle
Tinymists versions follow the [Semantic Versioning](https://semver.org/) scheme, in format of `MAJOR.MINOR.PATCH`. Besides, tinymist follows special rules for the version number:
- If a version is suffixed with `-rcN` (<picture>
<source media="(prefers-color-scheme: dark)" srcset="assets/images/introduction/frame_0.svg" /><img src="assets/images/introduction/frame_1.svg" alt="typst-frame" />
</picture>), e.g. `0.11.0-rc1` and `0.12.1-rc1`, it means this version is a release candidate. It is used to test publish script and E2E functionalities. These versions will not be published to the marketplace.
- If the `PATCH` number is odd, e.g. `0.11.1` and `0.12.3`, it means this version is a nightly release. The nightly release will use both [tinymist](https://github.com/Myriad-Dreamin/tinymist/tree/main) and [typst](https://github.com/typst/typst/tree/main) at **main branch**. They will be published as prerelease version to the marketplace. Note that in nightly releases, we change `#sys.version` to the next minor release to help develop documents with nightly features. For example, in tinymist nightly v0.12.1 or v0.12.3, the `#sys.version` is changed to `version(0, 13, 0)`.
- Otherwise, if the `PATCH` number is even, e.g. `0.11.0` and `0.12.2`, it means this version is a regular release. The regular release will always use the recent stable version of tinymist and typst.
The release cycle is as follows:
- If there is a typst version update, a new major or minor version will be released intermediately. This means tinymist will always align the minor version with typst.
- If there is at least a bug or feature added this week, a new patch version will be released.
- Compiles to PDF on save (configurable to as-you-type, or other options)
- [Editor tools](https://github.com/Myriad-Dreamin/tinymist/tree/main/tools/editor-tools):
- View a list of templates in template gallery.
- Click a button in template gallery to initialize a new project with a template.
## Installation
Follow the instructions to enable tinymist in your favorite editor.
- [VS Cod(e,ium)](https://myriad-dreamin.github.io/tinymist/frontend/vscode.html)
- [Neovim](https://myriad-dreamin.github.io/tinymist/frontend/neovim.html)
- [Emacs](https://myriad-dreamin.github.io/tinymist/frontend/emacs.html)
- [Sublime Text](https://myriad-dreamin.github.io/tinymist/frontend/sublime-text.html)
- [Helix](https://myriad-dreamin.github.io/tinymist/frontend/helix.html)
- [Zed](https://myriad-dreamin.github.io/tinymist/frontend/zed.html)
## Installing Regular/Nightly Prebuilds from GitHub
Note: if you are not knowing what is a regular/nightly release, please dont follow this section.
Besides published releases specific for each editors, you can also download the latest regular/nightly prebuilts from GitHub and install them manually.
- Regular prebuilts can be found in [GitHub Releases](https://github.com/Myriad-Dreamin/tinymist/releases).
- Nightly prebuilts can be found in [GitHub Actions](https://github.com/Myriad-Dreamin/tinymist/actions).
- (Suggested) Use the [tinymist-nightly-installer](https://github.com/hongjr03/tinymist-nightly-installer) to install the nightly prebuilts automatically.
- Unix (Bash):
```bash
curl -sSL https://github.com/hongjr03/tinymist-nightly-installer/releases/latest/download/run.sh | bash
```
- Windows (PowerShell):
```bash
iwr https://github.com/hongjr03/tinymist-nightly-installer/releases/latest/download/run.ps1 -UseBasicParsing | iex
```
- The prebuilts for other revisions can also be found manually. For example, if you are seeking a nightly release for the featured [PR: build: bump version to 0.11.17-rc1](https://github.com/Myriad-Dreamin/tinymist/pull/468), you could click and go to the [action page](https://github.com/Myriad-Dreamin/tinymist/actions/runs/10120639466) run for the related commits and download the artifacts.
To install extension file (the file with `.vsix` extension) manually, please <kbd>
Ctrl+Shift+X
</kbd> in the editor window and drop the downloaded vsix file into the opened extensions view.
## Documentation
See [Online Documentation](https://myriad-dreamin.github.io/tinymist/).
## Packaging
Stable Channel:
<a href="https://repology.org/project/tinymist/versions" target="_blank" rel="noopener noreferrer">
<img src="https://repology.org/badge/vertical-allrepos/tinymist.svg" alt="Packaging status" style="max-width: 100%; height: auto;" />
</a>
Nightly Channel:
<a href="https://repology.org/project/tinymist-nightly/versions" target="_blank" rel="noopener noreferrer">
<img src="https://repology.org/badge/vertical-allrepos/tinymist-nightly.svg" alt="Packaging status" style="max-width: 100%; height: auto;" />
</a>
## Roadmap
### Short Terms
To encourage contributions, we create many [Pull Requests](https://github.com/Myriad-Dreamin/tinymist/pulls) in draft to navigate short-term plans. They give you a hint of what or where to start in this large repository.
### Long Terms
We are planning to implement the following features in typst v0.14.0 or spare time in weekend:
- Type checking: complete the type checker.
- Periscope renderer: It is disabled since vscode reject to render SVGs containing foreignObjects.
- Inlay hint: It is disabled _by default_ because of performance issues.
- Find references of dictionary fields and named function arguments.
- Improve symbol views appearance.
- Improve package view.
- Navigate to symbols by clicking on the symbol name in the view.
- Automatically locate the symbol item in the view when viewing local documentation.
- Remember the recently invoked package commands, e.g. “Open Docs of @preview/cetz:0.3.1”, “Open directory of @preview/touying:0.5.3”.
- Improve label view.
- Group labels.
- Search labels.
- Keep (persist) group preferences.
- Improve Typst Preview.
- Pin drop-down: Set the file to preview in the drop-down for clients that doesnt support passing arguments to the preview command.
- Render in web worker (another thread) to reduce overhead on the electrons main thread.
- Spell checking: There is already a branch but no suitable (default) spell checking library is found.
- [typos](https://github.com/crate-ci/typos) is great for typst. [harper](https://github.com/Automattic/harper) looks promise.
If you are interested by any above features, please feel free to send Issues to discuss or PRs to implement to [GitHub.](https://github.com/Myriad-Dreamin/tinymist)
## Contributing
Please read the [CONTRIBUTING.md](CONTRIBUTING.md) file for contribution guidelines.
## Sponsoring
Tinymist thrives on community love and remains proudly independent. While we dont accept direct project funding, we warmly welcome support for our maintainers personal efforts. Please go to [Maintainers Page](/MAINTAINERS.md) and [Contributors Page](https://github.com/Myriad-Dreamin/tinymist/graphs/contributors) and find their personal pages for more information. It is also welcomed to directly ask questions about sponsoring on the [GitHub Issues](https://github.com/Myriad-Dreamin/tinymist/issues/new).
+ [VSCode](./editors/vscode/README.md)
## Acknowledgements
- Partially code is inherited from [typst-lsp](https://github.com/nvarner/typst-lsp)
- The [integrating](/editors/vscode#symbol-view) **offline** handwritten-stroke recognizer is powered by [Detypify](https://detypify.quarticcat.com/).
- The [integrating](/editors/vscode#preview-command) preview service is powered by [typst-preview](https://github.com/Enter-tainer/typst-preview).
- The [integrating](/editors/vscode#managing-local-packages) local package management functions are adopted from [vscode-typst-sync](https://github.com/OrangeX4/vscode-typst-sync).

View file

@ -1,4 +0,0 @@
files.extend-exclude = [
"locales/**/*",
"editors/vscode/e2e-workspaces/ieee-paper/ieee-tex.typ",
]

1
assets/.gitignore vendored
View file

@ -1 +0,0 @@
fonts

View file

@ -1,30 +0,0 @@
<svg class="typst-doc" viewBox="0 0 28.796833333333332 7.171500000000001" width="28.796833333333332pt" height="7.171500000000001pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:h5="http://www.w3.org/1999/xhtml">
<g>
<g transform="translate(0 7.171500000000001)">
<g class="typst-text" transform="scale(1, -1)">
<use xlink:href="#gE8D41A7B821A6DA6449385730041271C" x="0" fill="#f0f6fc" fill-rule="nonzero"/>
</g>
</g>
<g transform="translate(12.461166666666667 7.171500000000001)">
<g class="typst-text" transform="scale(1, -1)">
<use xlink:href="#g4026F2AE0915FEFF1AFA11899F67637E" x="0" fill="#f0f6fc" fill-rule="nonzero"/>
</g>
</g>
<g transform="translate(23.546833333333332 7.171500000000001)">
<g class="typst-text" transform="scale(1, -1)">
<use xlink:href="#g5C0D1D2E151935F3439EF1D4CB39DF87" x="0" fill="#f0f6fc" fill-rule="nonzero"/>
</g>
</g>
</g>
<defs id="glyph">
<symbol id="gE8D41A7B821A6DA6449385730041271C" overflow="visible">
<path d="M 9.072 7.1714997 C 8.872499 7.1714997 8.221499 7.14 8.022 7.14 C 7.8224998 7.14 7.161 7.1714997 6.9614997 7.1714997 C 6.804 7.1714997 6.7304997 7.0875 6.7304997 6.9195 C 6.7304997 6.825 6.804 6.7725 6.9614997 6.762 C 7.4024997 6.762 7.6229997 6.6254997 7.6229997 6.3525 C 7.6229997 6.2894998 7.6124997 6.216 7.6019998 6.1425 L 6.468 1.6485 L 4.2105 6.9509997 C 4.1265 7.1505 4.1054997 7.1714997 3.843 7.1714997 L 2.4465 7.1714997 C 2.1945 7.1714997 2.1 7.1505 2.1 6.9195 C 2.1 6.8145 2.2154999 6.762 2.4359999 6.762 C 2.8769999 6.762 3.0974998 6.7515 3.108 6.72 L 1.7114999 1.155 C 1.617 0.74549997 1.3755 0.5145 0.9975 0.44099998 C 0.7245 0.4095 0.4095 0.4515 0.4095 0.1575 C 0.4095 0.0525 0.4725 0 0.588 0 C 0.777 0 1.428 0.0315 1.6274999 0.0315 C 1.827 0.0315 2.499 0 2.6985 0 C 2.856 0 2.9294999 0.084 2.9294999 0.252 C 2.9294999 0.3465 2.8455 0.399 2.6775 0.4095 C 2.247 0.42 2.037 0.5565 2.037 0.819 C 2.037 0.8715 2.0475 0.945 2.0685 1.05 L 3.4229999 6.4154997 L 6.048 0.22049999 C 6.111 0.0735 6.1949997 0 6.2894998 0 C 6.384 0 6.447 0.084 6.489 0.252 L 7.9484997 6.027 C 8.085 6.5625 8.379 6.7515 9.03 6.762 C 9.177 6.7725 9.2505 6.8564997 9.2505 7.0245 C 9.219 7.119 9.2085 7.1714997 9.072 7.1714997 Z "/>
</symbol>
<symbol id="g4026F2AE0915FEFF1AFA11899F67637E" overflow="visible">
<path d="M 7.203 2.3834999 C 7.308 2.4359999 7.3605 2.5095 7.3605 2.625 C 7.3605 2.7405 7.308 2.814 7.203 2.8665 L 1.176 5.7225 C 1.1445 5.733 1.1025 5.7434998 1.0605 5.7434998 C 0.8925 5.7434998 0.8085 5.6595 0.8085 5.481 C 0.8085 5.3865 0.861 5.3129997 0.9555 5.271 L 6.5625 2.625 L 0.9555 -0.021 C 0.861 -0.063 0.8085 -0.1365 0.8085 -0.23099999 C 0.8085 -0.4095 0.8925 -0.4935 1.0605 -0.4935 C 1.1025 -0.4935 1.1445 -0.48299998 1.176 -0.4725 Z "/>
</symbol>
<symbol id="g5C0D1D2E151935F3439EF1D4CB39DF87" overflow="visible">
<path d="M 2.6145 -0.23099999 C 4.095 -0.23099999 4.83 0.96599996 4.83 3.36 C 4.83 4.9665 4.494 6.0375 3.8325 6.5625 C 3.465 6.8459997 3.0555 6.993 2.625 6.993 C 1.1445 6.993 0.4095 5.7855 0.4095 3.36 C 0.4095 1.428 0.92399997 -0.23099999 2.6145 -0.23099999 Z M 3.7905 5.502 C 3.8639998 5.1345 3.8955 4.4625 3.8955 3.486 C 3.8955 2.52 3.8535 1.806 3.78 1.344 C 3.6434999 0.504 3.2549999 0.084 2.6145 0.084 C 2.373 0.084 2.1315 0.1785 1.911 0.357 C 1.6274999 0.5985 1.4595 1.092 1.386 1.8479999 C 1.3544999 2.1104999 1.344 2.6564999 1.344 3.486 C 1.344 4.3995 1.3755 5.04 1.428 5.3865 C 1.5225 5.9639997 1.7114999 6.3315 2.0055 6.489 C 2.2365 6.615 2.4359999 6.678 2.6145 6.678 C 3.297 6.678 3.675 6.1215 3.7905 5.502 Z "/>
</symbol>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 3.7 KiB

View file

@ -1,30 +0,0 @@
<svg class="typst-doc" viewBox="0 0 28.796833333333332 7.171500000000001" width="28.796833333333332pt" height="7.171500000000001pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:h5="http://www.w3.org/1999/xhtml">
<g>
<g transform="translate(0 7.171500000000001)">
<g class="typst-text" transform="scale(1, -1)">
<use xlink:href="#gE8D41A7B821A6DA6449385730041271C" x="0" fill="#262625" fill-rule="nonzero"/>
</g>
</g>
<g transform="translate(12.461166666666667 7.171500000000001)">
<g class="typst-text" transform="scale(1, -1)">
<use xlink:href="#g4026F2AE0915FEFF1AFA11899F67637E" x="0" fill="#262625" fill-rule="nonzero"/>
</g>
</g>
<g transform="translate(23.546833333333332 7.171500000000001)">
<g class="typst-text" transform="scale(1, -1)">
<use xlink:href="#g5C0D1D2E151935F3439EF1D4CB39DF87" x="0" fill="#262625" fill-rule="nonzero"/>
</g>
</g>
</g>
<defs id="glyph">
<symbol id="gE8D41A7B821A6DA6449385730041271C" overflow="visible">
<path d="M 9.072 7.1714997 C 8.872499 7.1714997 8.221499 7.14 8.022 7.14 C 7.8224998 7.14 7.161 7.1714997 6.9614997 7.1714997 C 6.804 7.1714997 6.7304997 7.0875 6.7304997 6.9195 C 6.7304997 6.825 6.804 6.7725 6.9614997 6.762 C 7.4024997 6.762 7.6229997 6.6254997 7.6229997 6.3525 C 7.6229997 6.2894998 7.6124997 6.216 7.6019998 6.1425 L 6.468 1.6485 L 4.2105 6.9509997 C 4.1265 7.1505 4.1054997 7.1714997 3.843 7.1714997 L 2.4465 7.1714997 C 2.1945 7.1714997 2.1 7.1505 2.1 6.9195 C 2.1 6.8145 2.2154999 6.762 2.4359999 6.762 C 2.8769999 6.762 3.0974998 6.7515 3.108 6.72 L 1.7114999 1.155 C 1.617 0.74549997 1.3755 0.5145 0.9975 0.44099998 C 0.7245 0.4095 0.4095 0.4515 0.4095 0.1575 C 0.4095 0.0525 0.4725 0 0.588 0 C 0.777 0 1.428 0.0315 1.6274999 0.0315 C 1.827 0.0315 2.499 0 2.6985 0 C 2.856 0 2.9294999 0.084 2.9294999 0.252 C 2.9294999 0.3465 2.8455 0.399 2.6775 0.4095 C 2.247 0.42 2.037 0.5565 2.037 0.819 C 2.037 0.8715 2.0475 0.945 2.0685 1.05 L 3.4229999 6.4154997 L 6.048 0.22049999 C 6.111 0.0735 6.1949997 0 6.2894998 0 C 6.384 0 6.447 0.084 6.489 0.252 L 7.9484997 6.027 C 8.085 6.5625 8.379 6.7515 9.03 6.762 C 9.177 6.7725 9.2505 6.8564997 9.2505 7.0245 C 9.219 7.119 9.2085 7.1714997 9.072 7.1714997 Z "/>
</symbol>
<symbol id="g4026F2AE0915FEFF1AFA11899F67637E" overflow="visible">
<path d="M 7.203 2.3834999 C 7.308 2.4359999 7.3605 2.5095 7.3605 2.625 C 7.3605 2.7405 7.308 2.814 7.203 2.8665 L 1.176 5.7225 C 1.1445 5.733 1.1025 5.7434998 1.0605 5.7434998 C 0.8925 5.7434998 0.8085 5.6595 0.8085 5.481 C 0.8085 5.3865 0.861 5.3129997 0.9555 5.271 L 6.5625 2.625 L 0.9555 -0.021 C 0.861 -0.063 0.8085 -0.1365 0.8085 -0.23099999 C 0.8085 -0.4095 0.8925 -0.4935 1.0605 -0.4935 C 1.1025 -0.4935 1.1445 -0.48299998 1.176 -0.4725 Z "/>
</symbol>
<symbol id="g5C0D1D2E151935F3439EF1D4CB39DF87" overflow="visible">
<path d="M 2.6145 -0.23099999 C 4.095 -0.23099999 4.83 0.96599996 4.83 3.36 C 4.83 4.9665 4.494 6.0375 3.8325 6.5625 C 3.465 6.8459997 3.0555 6.993 2.625 6.993 C 1.1445 6.993 0.4095 5.7855 0.4095 3.36 C 0.4095 1.428 0.92399997 -0.23099999 2.6145 -0.23099999 Z M 3.7905 5.502 C 3.8639998 5.1345 3.8955 4.4625 3.8955 3.486 C 3.8955 2.52 3.8535 1.806 3.78 1.344 C 3.6434999 0.504 3.2549999 0.084 2.6145 0.084 C 2.373 0.084 2.1315 0.1785 1.911 0.357 C 1.6274999 0.5985 1.4595 1.092 1.386 1.8479999 C 1.3544999 2.1104999 1.344 2.6564999 1.344 3.486 C 1.344 4.3995 1.3755 5.04 1.428 5.3865 C 1.5225 5.9639997 1.7114999 6.3315 2.0055 6.489 C 2.2365 6.615 2.4359999 6.678 2.6145 6.678 C 3.297 6.678 3.675 6.1215 3.7905 5.502 Z "/>
</symbol>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 3.7 KiB

View file

@ -1,21 +0,0 @@
[package]
name = "tinymist-bench-font-load"
description = "Font loading bench for tinymist."
authors.workspace = true
version.workspace = true
license.workspace = true
edition.workspace = true
homepage.workspace = true
repository.workspace = true
[dependencies]
divan.workspace = true
tinymist.workspace = true
[[bench]]
name = "tinymist-bench-font-load"
path = "src/load.rs"
harness = false
[features]
the-thesis = []

View file

@ -1,39 +0,0 @@
use std::sync::Arc;
use tinymist::{Config, project::LspUniverseBuilder};
fn main() {
// initialize global variables
// Run registered benchmarks.
divan::main();
}
// Checks font loading performance of embedded fonts
#[divan::bench]
fn load_embedded() {
let _embedded_fonts = Arc::new(LspUniverseBuilder::only_embedded_fonts().unwrap());
}
// Checks font loading performance of system fonts
#[divan::bench]
fn load_system() {
let config = Config::default();
let _fonts = config.fonts();
}
/*
Without Parallelization
Timer precision: 17 ns
tinymist_bench_font_load fastest slowest median mean samples iters
load_embedded 1.167 ms 1.697 ms 1.176 ms 1.188 ms 100 100
load_system 111.8 ms 123 ms 113.6 ms 114.3 ms 100 100
With Parallelization
Timer precision: 17 ns
tinymist_bench_font_load fastest slowest median mean samples iters
load_embedded 130.8 µs 1.164 ms 157 µs 170.3 µs 100 100
load_system 14.44 ms 18.22 ms 15.37 ms 15.54 ms 100 100
*/

View file

@ -1,39 +0,0 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.5/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": false
},
"files": {
"ignoreUnknown": false
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"javascript": {
"formatter": {
"arrowParentheses": "always",
"bracketSpacing": true,
"quoteStyle": "double",
"semicolons": "always",
"trailingCommas": "all"
}
},
"assist": {
"actions": {
"source": {
"organizeImports": "on"
}
}
}
}

View file

@ -1,6 +0,0 @@
tinymist-*.vsix
*.log
test-dist
.vscode-test
coverage
out/

View file

@ -1,12 +0,0 @@
**
!out/tinymist-docs.pdf
!out/extension.js
!out/extension.web.js
!out/server.js
!package.json
!package-lock.json
!icons/**
!syntaxes/**
!README.md
!LICENSE
!CHANGELOG.md

View file

@ -1,11 +0,0 @@
# Change Log
All notable changes to the "tinymist-html-ext" extension will be documented in this file.
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
The changelog lines unspecified with authors are all written by the @Myriad-Dreamin.
## v0.13.0 - [2025-02-22]
Initial release of the extension.

View file

@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2023-2025 Myriad Dreamin, Nathan Varner
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -1,16 +0,0 @@
import { build } from "esbuild";
import * as fs from "fs";
if (!fs.existsSync("./out/extension.web.js")) {
fs.mkdirSync("./out", { recursive: true });
fs.writeFileSync("./out/extension.web.js", "");
}
build({
entryPoints: ["./src/extension.mts", "./src/server.mts"],
bundle: true,
outdir: "./out",
external: ["vscode"],
format: "cjs",
platform: "node",
}).catch(() => process.exit(1));

View file

@ -1,29 +0,0 @@
import { build } from "esbuild";
import { polyfillNode } from "esbuild-plugin-polyfill-node";
import * as fs from "fs";
if (!fs.existsSync("./out/extension.js")) {
fs.mkdirSync("./out", { recursive: true });
fs.writeFileSync("./out/extension.js", "");
}
build({
entryPoints: ["./src/extension.web.ts"],
bundle: true,
outfile: "./out/extension.web.js",
external: ["vscode"],
format: "cjs",
target: ["es2020", "chrome61", "edge18", "firefox60"],
// Node.js global to browser globalThis
define: {
global: "globalThis",
},
plugins: [
polyfillNode({
polyfills: {
crypto: "empty",
},
// Options (optional)
}),
],
}).catch(() => process.exit(1));

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

View file

@ -1,80 +0,0 @@
{
"name": "tinymist-vscode-html",
"version": "0.14.6-rc1",
"description": "Extending Typst with HTML features",
"keywords": [
"html",
"typst",
"language-server"
],
"categories": [
"Programming Languages"
],
"repository": {
"type": "git",
"url": "https://github.com/Myriad-Dreamin/tinymist.git"
},
"displayName": "Tinymist Typst HTML",
"author": "Myriad-Dreamin",
"contributors": [
"Myriad-Dreamin"
],
"publisher": "myriad-dreamin",
"license": "Apache-2.0",
"engines": {
"vscode": "^1.97.0"
},
"activationEvents": [
"onLanguage:typst"
],
"main": "./out/extension.js",
"browser": "./out/extension.web.js",
"icon": "./icons/ti-white.png",
"contributes": {
"commands": [
{
"command": "tinymist.showHtmlExtensionLog",
"title": "Show Log of the HTML extension",
"description": "Show log of the tinymist HTML extension",
"category": "Typst",
"icon": "$(list-flat)"
}
]
},
"scripts": {
"build-web-base": "node esbuild.web.mjs",
"build-system-base": "node esbuild.system.mjs",
"build-base": "yarn run build-web-base && yarn run build-system-base",
"compile-shared": "node scripts/check-version.mjs && node scripts/postinstall.cjs",
"compile:web": "yarn run build-web-base -- --minify && yarn run compile-shared",
"compile:system": "yarn run build-system-base -- --minify && yarn run compile-shared",
"package": "npx @vscode/vsce package --yarn",
"compile": "yarn run compile:system",
"watch": "yarn run build-system-base -- --sourcemap --watch",
"check": "tsc --noEmit",
"format-check": "prettier --check .",
"format": "prettier --write .",
"test": "rimraf test-dist/ && tsc -p tsconfig.test.json && node test-dist/test/runTests.js"
},
"dependencies": {
"css": "^3.0.0",
"esbuild-plugin-polyfill-node": "^0.3.0",
"lodash.flow": "^3.5.0",
"vscode-languageclient": "^9.0.0",
"vscode-languageserver": "^9.0.0",
"xxhashjs": "^0.2.2"
},
"devDependencies": {
"@types/chai": "^5.0.1",
"@types/css": "^0.0.38",
"@types/mocha": "^10.0.1",
"@types/node": "^22.13.4",
"@types/vscode": "^1.97.0",
"@vscode/test-electron": "^2.3.9",
"@vscode/vsce": "^2.22.0",
"chai": "^5.1.1",
"mocha": "^10.2.0",
"ovsx": "^0.8.3",
"vscode-html-languageservice": "^5.3.1"
}
}

View file

@ -1,15 +0,0 @@
import { readFileSync } from "fs";
function check() {
const cargoToml = readFileSync("../../../../Cargo.toml", "utf8");
const cargoVersion = cargoToml.match(/version = "(.*?)"/)[1];
const pkgVersion = JSON.parse(readFileSync("package.json", "utf8")).version;
if (cargoVersion !== pkgVersion) {
throw new Error(
`Version mismatch: ${cargoVersion} (in Cargo.toml) !== ${pkgVersion} (in package.json)`,
);
}
}
check();

View file

@ -1,9 +0,0 @@
MIT License
Copyright (c) 2023 Anders Ellenshøj Andersen <andersa@atlab.dk>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,35 +0,0 @@
let XXH = require("xxhashjs").h32;
export function flatten<T>(nestedArray: T[][]): T[] {
if (nestedArray.length === 0) {
throw new RangeError("Can't flatten an empty array.");
} else {
return nestedArray.reduce((a, b) => a.concat(b));
}
}
export function distinct<T>(items: T[] | Thenable<T[]>): Thenable<T[]> {
return Promise.resolve(items).then((items) => Array.from(new Set(items)));
}
export function distinctByXXHash<T>(items: T[] | Thenable<T[]>): Thenable<T[]> {
const initialValue = {
distinctItems: <T[]>[],
hashSet: new Set(),
};
const accumulatorPromise = Promise.resolve(items).then((items) =>
items.reduce((acc, item) => {
const hash = XXH(item, 0x1337).toNumber();
if (!acc.hashSet.has(hash)) {
acc.distinctItems.push(item);
acc.hashSet.add(hash);
}
return acc;
}, initialValue),
);
return accumulatorPromise.then((accumulator) => accumulator.distinctItems);
}

View file

@ -1,34 +0,0 @@
import { workspace, window } from "vscode";
// @ts-ignore
import flow from "lodash.flow";
import uriFilesReader from "./uriFilesReader";
import { distinct, distinctByXXHash } from "./arrayUtils";
import { parseCssTexts, getCSSRules, getCSSSelectors, getCSSClasses } from "./cssUtils";
const styleSheetsReader = flow(uriFilesReader, distinctByXXHash, parseCssTexts);
const distinctCSSClassesExtractor = flow(getCSSRules, getCSSSelectors, getCSSClasses, distinct);
export default function (): Thenable<string[]> {
const startTime = process.hrtime();
return styleSheetsReader(
workspace.findFiles("**/*.css", ""),
workspace.getConfiguration("files").get("encoding", "utf8"),
).then((parseResult: any) => {
return distinctCSSClassesExtractor(parseResult.styleSheets).then((distinctCssClasses: any) => {
const elapsedTime = process.hrtime(startTime);
console.log(`Elapsed time: ${elapsedTime[0]} s ${Math.trunc(elapsedTime[1] / 1e6)} ms`);
console.log(`Files processed: ${parseResult.styleSheets.length}`);
console.log(`Skipped due to parse errors: ${parseResult.unparsable.length}`);
console.log(`CSS classes discovered: ${distinctCssClasses.length}`);
window.setStatusBarMessage(
`HTML Class Suggestions processed ${parseResult.styleSheets.length} distinct css files and discovered ${distinctCssClasses.length} css classes.`,
10000,
);
return distinctCssClasses;
});
});
}

View file

@ -1,39 +0,0 @@
"use strict";
import * as vscode from "vscode";
import aggregator from "./cssAggregator";
export class CssCompletionItemProvider {
public completionItems?: PromiseLike<vscode.CompletionItem[]>;
constructor() {
this.refreshCompletionItems();
}
// public provideCompletionItems(
// document: vscode.TextDocument,
// position: vscode.Position,
// token: vscode.CancellationToken,
// ): Thenable<vscode.CompletionItem[]> {
// if (canTriggerCompletion(document, position)) {
// return this.completionItems as PromiseLike<vscode.CompletionItem[]>;
// } else {
// return Promise.reject<vscode.CompletionItem[]>("Not inside html class attribute.");
// }
// }
public refreshCompletionItems() {
this.completionItems = aggregator().then((cssClasses) => {
const completionItems = cssClasses.map((cssClass) => {
const completionItem = new vscode.CompletionItem(cssClass);
completionItem.detail = `Insert ${cssClass}`;
completionItem.insertText = cssClass;
completionItem.kind = vscode.CompletionItemKind.Value;
// make sure our completion item group are first
completionItem.preselect = true;
return completionItem;
});
return completionItems;
});
}
}

View file

@ -1,105 +0,0 @@
import { parse, Stylesheet, Rule, Media } from "css";
import { flatten } from "./arrayUtils";
export interface CSSTextsParseResult {
styleSheets: Stylesheet[];
unparsable: string[];
}
export function parseCssTexts(
cssTexts: string[] | Thenable<string[]>,
): Thenable<CSSTextsParseResult> {
const initialValue = {
styleSheets: <Stylesheet[]>[],
unparsable: <string[]>[],
};
return Promise.resolve(cssTexts).then((cssTexts) =>
cssTexts.reduce((acc, cssText) => {
try {
acc.styleSheets.push(parse(cssText));
} catch (error) {
acc.unparsable.push(cssText);
}
return acc;
}, initialValue),
);
}
export function getCSSRules(styleSheets: Stylesheet[] | Thenable<Stylesheet[]>): Thenable<Rule[]> {
return Promise.resolve(styleSheets).then((styleSheets) =>
styleSheets.reduce((acc, styleSheet) => {
return acc.concat(findRootRules(styleSheet), findMediaRules(styleSheet));
}, [] as Rule[]),
);
}
export function getCSSSelectors(rules: Rule[] | Thenable<Rule[]>): Thenable<string[]> {
return Promise.resolve(rules).then((rules) => {
if (rules.length > 0) {
return flatten(rules.map((rule) => rule.selectors!)).filter(
(value) => value && value.length > 0,
);
} else {
return [];
}
});
}
export function getCSSClasses(selectors: string[] | Thenable<string[]>): Thenable<string[]> {
return Promise.resolve(selectors).then((selectors) =>
selectors.reduce((acc, selector) => {
const className = findClassName(selector);
if (className && className.length > 0) {
acc.push(sanitizeClassName(className));
}
return acc;
}, [] as string[]),
);
}
export function findRootRules(cssAST: Stylesheet): Rule[] {
// @ts-ignore
return cssAST.stylesheet!.rules.filter((node) => (<Rule>node).type === "rule");
}
export function findMediaRules(cssAST: Stylesheet): Rule[] {
let mediaNodes = <Rule[]>cssAST.stylesheet!.rules.filter((node) => {
// @ts-ignore
return (<Rule>node).type === "media";
});
if (mediaNodes.length > 0) {
// @ts-ignore
return flatten(mediaNodes.map((node) => (<Media>node).rules!));
} else {
return [];
}
}
export function findClassName(selector: string): string {
let classNameStartIndex = selector.lastIndexOf(".");
if (classNameStartIndex >= 0) {
let classText = selector.substr(classNameStartIndex + 1);
// Search for one of ' ', '[', ':' or '>', that isn't escaped with a backslash
let classNameEndIndex = classText.search(/[^\\][\s\[:>]/);
if (classNameEndIndex >= 0) {
return classText.substr(0, classNameEndIndex + 1);
} else {
return classText;
}
} else {
return "";
}
}
export function sanitizeClassName(className: string): string {
return className.replace(/\\[!"#$%&'()*+,\-./:;<=>?@[\\\]^`{|}~]/g, (substr, ...args) => {
if (args.length === 2) {
return substr.slice(1);
} else {
return substr;
}
});
}

View file

@ -1,23 +0,0 @@
import * as path from 'path';
import { runTests } from '@vscode/test-electron';
async function main() {
try {
// The folder containing the Extension Manifest package.json
// Passed to `--extensionDevelopmentPath`
const extensionDevelopmentPath = path.resolve(__dirname, '../../');
// The path to test runner
// Passed to --extensionTestsPath
const extensionTestsPath = path.resolve(__dirname, './suite/index');
// Download VS Code, unzip it and run the integration test
await runTests({ extensionDevelopmentPath, extensionTestsPath });
} catch (err) {
console.error('Failed to run tests', err);
process.exit(1);
}
}
main();

View file

@ -1,38 +0,0 @@
import { findClassName, sanitizeClassName } from '../../cssUtils';
import * as assert from 'assert';
suite('Test cssUtils', () => {
suite('#findClassName', () => {
test('finds \'container\' in \'.container\'', () => {
assert.equal(findClassName('.container'), 'container');
});
test('finds \'bg-warning\' in \'a.bg-warning:hover\'', () => {
assert.equal(findClassName('a.bg-warning:hover'), 'bg-warning');
});
test('finds \'u-1\\/2\' in \'.u-1\\/2\'', () => {
assert.equal(findClassName('.u-1\\/2'), 'u-1\\/2');
});
test('finds \'ratio-16\\:9\' in \'.ratio-16\\:9\'', () => {
assert.equal(findClassName('.ratio-16\\:9'), 'ratio-16\\:9');
});
test('finds \'margin\\@palm\' in \'.margin\\@palm\'', () => {
assert.equal(findClassName('.margin\\@palm'), 'margin\\@palm');
});
});
suite('#sanitizeClassName', () => {
test('sanitizes \'u-1\\/2\' to \'u-1/2\'', () =>{
assert.equal(sanitizeClassName('u-1\\/2'), 'u-1/2');
});
test('sanitizes \'ratio-16\\:9\' to \'ratio-16:9\'', () =>{
assert.equal(sanitizeClassName('ratio-16\\:9'), 'ratio-16:9');
});
test('sanitizes \'margin\\@palm\' to \'margin@palm\'', () =>{
assert.equal(sanitizeClassName('margin\\@palm'), 'margin@palm');
});
test('sanitizes \'foo-1\\/2\\@bar\' to \'foo-1/2@bar\'', () =>{
assert.equal(sanitizeClassName('foo-1\\/2\\@bar'), 'foo-1/2@bar');
});
});
});

View file

@ -1,38 +0,0 @@
import * as path from "path";
import * as Mocha from "mocha";
import * as glob from "glob";
export function run(): Promise<void> {
// Create the mocha test
const mocha = new Mocha({
ui: "tdd",
color: true,
});
const testsRoot = path.resolve(__dirname, "..");
return new Promise((c, e) => {
glob("**/**.test.js", { cwd: testsRoot }, (err, files) => {
if (err) {
return e(err);
}
// Add files to the test suite
files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f)));
try {
// Run the mocha test
mocha.run((failures) => {
if (failures > 0) {
e(new Error(`${failures} tests failed.`));
} else {
c();
}
});
} catch (err) {
console.error(err);
e(err);
}
});
});
}

View file

@ -1,14 +0,0 @@
import uriFilesReader from "../../uriFilesReader";
import * as assert from "assert";
import { Uri } from "vscode";
suite("Test uriFilesReader", () => {
test("Can't load http protocol", () => {
const httpsUri = Uri.parse("https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css");
return uriFilesReader([httpsUri], "utf8").then((data: any) => {
assert(false, "Expected promise to be rejected.");
},(err: any) => {
assert(err.code === "ENOENT");
});
});
});

View file

@ -1,16 +0,0 @@
import { Uri } from "vscode";
import { readFile } from "fs";
export default (uris: Uri[]|Thenable<Uri[]>, encoding: BufferEncoding): Thenable<string[]> => {
return Promise.resolve(uris).then(uris => {
return Promise.all(uris.map(uri => new Promise<string>((resolve, reject) => {
readFile(uri.fsPath, encoding, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data.toString());
}
});
})));
});
};

View file

@ -1,224 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
interface EmbeddedRegion {
languageId: string | undefined;
start: number;
end: number;
attributeValue?: boolean;
}
enum TokenKind {
Unknown,
Colon,
String,
Identifier,
}
class BackScanner {
currentToken: TokenKind = TokenKind.Unknown;
tokenContent: string = "";
constructor(
private documentText: string,
private offset: number,
) {
this.scanBack();
}
getCurrentToken() {
return this.currentToken;
}
scanBack() {
let i = this.offset;
this.currentToken = TokenKind.Unknown;
while (i >= 0) {
const ch = this.documentText[i];
i--;
// console.log("scanBack", ch, this.currentToken, this.tokenContent);
if (this.currentToken === TokenKind.Unknown) {
if (ch === ":") {
this.currentToken = TokenKind.Colon;
this.tokenContent = ch;
break;
} else if (ch === '"') {
this.currentToken = TokenKind.String;
this.tokenContent = ch;
} else if (/[a-zA-Z0-9\-]/.test(ch)) {
this.currentToken = TokenKind.Identifier;
this.tokenContent = ch;
} else if (/\s/.test(ch)) {
// ignore
} else {
break;
}
} else if (this.currentToken === TokenKind.String) {
this.tokenContent = ch + this.tokenContent;
if (ch === '"') {
break;
}
} else if (this.currentToken === TokenKind.Identifier) {
if (/[a-zA-Z0-9\-]/.test(ch)) {
this.tokenContent = ch + this.tokenContent;
} else {
break;
}
}
}
this.offset = i;
}
}
export function isInsideClassAttribute(documentText: string, offset: number) {
console.log("isInsideClassAttribute", offset);
// string start
let start = offset - 1;
while (start >= 0) {
if (documentText[start] === '"') {
let shashCount = 0;
while (start > 0) {
if (documentText[start - 1] === "\\") {
shashCount++;
start--;
} else {
break;
}
}
if (shashCount % 2 === 0) {
break;
}
start--;
} else {
start--;
}
}
if (start >= 0 && documentText[start] === '"') {
start -= 1;
// find class attribute
const reverseScanner = new BackScanner(documentText, start);
if (reverseScanner.getCurrentToken() !== TokenKind.Colon) {
return false;
}
reverseScanner.scanBack();
if (reverseScanner.getCurrentToken() === TokenKind.Identifier) {
console.log("found class attribute", reverseScanner.tokenContent);
return reverseScanner.tokenContent === "class";
}
if (reverseScanner.getCurrentToken() === TokenKind.String) {
console.log("found class attribute", reverseScanner.tokenContent);
return reverseScanner.tokenContent === '"class"';
}
}
return false;
}
export function parseRawBlockRegion(
documentText: string,
offset: number,
): EmbeddedRegion | undefined {
let start = offset - 1;
while (start >= 0) {
if (documentText[start] === "`") {
start -= 2;
if (start < 0 || documentText.slice(start, start + 3) !== "```") {
break;
}
let languageOffset = start + 3;
let backtickStart = start;
while (backtickStart > 0 && documentText[backtickStart - 1] === "`") {
backtickStart -= 1;
}
let numOfBackticks = languageOffset - backtickStart;
let languageStart = languageOffset;
while (languageOffset < offset) {
if (/\s/.test(documentText[languageOffset])) {
break;
}
languageOffset++;
}
let languageId = documentText.slice(languageStart, languageOffset);
console.log("numOfBackticks", numOfBackticks, languageOffset, languageId);
let rawOffset = languageOffset;
let rawEnd = languageOffset;
let accumulatedBacktick = 0;
while (rawEnd < documentText.length) {
const isBacktick = documentText[rawEnd] === "`";
rawEnd++;
if (isBacktick) {
accumulatedBacktick++;
} else {
if (accumulatedBacktick >= numOfBackticks) {
break;
}
accumulatedBacktick = 0;
}
}
if (accumulatedBacktick > rawEnd) {
return;
}
rawEnd -= accumulatedBacktick;
const rawContent = documentText.slice(rawOffset, rawEnd);
console.log("raw content", languageId, rawOffset, rawEnd, rawContent);
// return [languageId, rawOffset, rawEnd];
return {
languageId,
start: rawOffset,
end: rawEnd,
};
}
start--;
}
return;
}
/**
* Extract embedded regions from a document
*
* @param documentText The content of the document
* @param regions The regions to embed
* @param langId The language id to extract
* @returns The content of the document with the regions embedded
*/
export function getVirtualContent(
documentText: string,
regions: EmbeddedRegion[],
langId: string,
): string {
// Keeps space.
let content = documentText
.split("\n")
.map((line) => {
return " ".repeat(line.length);
})
.join("\n");
regions.forEach((r) => {
if (r.languageId === langId) {
content =
content.slice(0, r.start) + documentText.slice(r.start, r.end) + content.slice(r.end);
}
});
return content;
}

View file

@ -1,142 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import * as path from "path";
import * as vscode from "vscode";
import { commands, CompletionList, ExtensionContext, Uri } from "vscode";
import {
type LanguageClientOptions,
type ServerOptions,
LanguageClient,
TransportKind,
} from "vscode-languageclient/node";
import { getVirtualContent, isInsideClassAttribute, parseRawBlockRegion } from "./embeddedSupport";
import { cssActivate } from "./features/css";
let client: LanguageClient;
export function activate(context: ExtensionContext) {
const tinymistExtension = vscode.extensions.getExtension("myriad-dreamin.tinymist");
if (!tinymistExtension) {
void vscode.window.showWarningMessage(
"Tinymist HTML:\n\nTinymist LSP feature is required. Please install Tinymist Typst Extension (myriad-dreamin.tinymist).",
);
}
const provider = cssActivate(context);
// The server is implemented in node
const serverModule = context.asAbsolutePath(path.join("out", "server.js"));
// If the extension is launched in debug mode then the debug server options are used
// Otherwise the run options are used
const serverOptions: ServerOptions = {
run: { module: serverModule, transport: TransportKind.ipc },
debug: {
module: serverModule,
transport: TransportKind.ipc,
},
};
const virtualDocumentContents = new Map<string, string>();
const removeSuffix = (uri: string) => uri.replace(/\.(html|css)$/, "");
vscode.workspace.registerTextDocumentContentProvider("embedded-content", {
provideTextDocumentContent: (uri) => {
const originalUri = removeSuffix(uri.path.slice(1));
const decodedUri = decodeURIComponent(originalUri);
console.log("provideTextDocumentContent gg", uri.path, originalUri, decodedUri);
return virtualDocumentContents.get(decodedUri);
},
});
const clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: "file", language: "typst" }],
middleware: {
provideCompletionItem: async (document, position, context, token, next) => {
const res = await vscode.commands.executeCommand<
[{ mode: "math" | "markup" | "code" | "comment" | "string" | "raw" }]
>("tinymist.interactCodeContext", {
textDocument: {
uri: document.uri.toString(),
},
query: [
{
kind: "modeAt",
position: {
line: position.line,
character: position.character,
},
},
],
});
const inString = res[0]?.mode === "string";
const inRaw = res[0]?.mode === "raw";
// If completes the content of a `class` attribute, completes classes found in the css
// files.
if (inString && isInsideClassAttribute(document.getText(), document.offsetAt(position))) {
return provider.completionItems;
}
if (!inRaw) {
return await next(document, position, context, token);
}
// If not in `<style>`, do not perform request forwarding
const virtualContent = parseRawBlockRegion(document.getText(), document.offsetAt(position));
if (!virtualContent) {
return await next(document, position, context, token);
}
const langId = virtualContent.languageId;
if (langId !== "html" && langId !== "css") {
return await next(document, position, context, token);
}
const originalUri = document.uri.toString(true);
virtualDocumentContents.set(
originalUri,
getVirtualContent(document.getText(), [virtualContent], langId!),
);
const vdocUriString = `embedded-content://${langId}/${encodeURIComponent(originalUri)}.${langId}`;
const vdocUri = Uri.parse(vdocUriString);
return await commands.executeCommand<CompletionList>(
"vscode.executeCompletionItemProvider",
vdocUri,
position,
context.triggerCharacter,
);
},
},
};
// Create the language client and start the client.
client = new LanguageClient(
"TinymistTypstHTMLExtension",
"Tinymist Typst HTML Extension",
serverOptions,
clientOptions,
);
context.subscriptions.push(
vscode.commands.registerCommand("tinymist-html-ext.showLog", () => {
client.outputChannel?.show();
}),
);
// Start the client. This will also launch the server
client.start();
}
export function deactivate(): Thenable<void> | undefined {
if (!client) {
return undefined;
}
return client.stop();
}

View file

@ -1,16 +0,0 @@
import * as vscode from "vscode";
import { CssCompletionItemProvider } from "../css/cssCompletionItemProvider";
export function cssActivate(context: vscode.ExtensionContext) {
let provider = new CssCompletionItemProvider();
context.subscriptions.push(
vscode.workspace.onDidSaveTextDocument((e) => {
if (e.languageId === "css") {
provider.refreshCompletionItems();
}
}),
);
return provider;
}

View file

@ -1,62 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import { getLanguageService } from "vscode-html-languageservice/lib/esm/htmlLanguageService";
import {
createConnection,
InitializeParams,
ProposedFeatures,
TextDocuments,
TextDocumentSyncKind,
} from "vscode-languageserver/node";
import { TextDocument } from "vscode-languageserver-textdocument";
// Create a connection for the server. The connection uses Node's IPC as a transport.
// Also include all preview / proposed LSP features.
const connection = createConnection(ProposedFeatures.all);
// Create a simple text document manager. The text document manager
// supports full document sync only
const documents = new TextDocuments(TextDocument);
const htmlLanguageService = getLanguageService();
connection.onInitialize((_params: InitializeParams) => {
console.log("initialize server");
return {
capabilities: {
textDocumentSync: TextDocumentSyncKind.Full,
// Tell the client that the server supports code completion
completionProvider: {
triggerCharacters: [
..."abcdefghijklmnopqrstuvwxyz".split(""),
..."ABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""),
],
resolveProvider: false,
},
},
};
});
connection.onCompletion(async (textDocumentPosition, _token) => {
console.log(
"provideCompletionItem server",
textDocumentPosition.textDocument.uri,
textDocumentPosition.position,
);
const document = documents.get(textDocumentPosition.textDocument.uri);
if (!document) {
return null;
}
return htmlLanguageService.doComplete(
document,
textDocumentPosition.position,
htmlLanguageService.parseHTMLDocument(document),
);
});
documents.listen(connection);
connection.listen();

View file

@ -1,14 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"types": ["mocha", "chai", "node"],
"target": "es2020",
"lib": ["es2020"],
"outDir": "out",
"rootDir": "src",
"sourceMap": true,
"strict": true
},
"include": ["src", "scripts"],
"exclude": ["node_modules/*", "../../node_modules/*"]
}

View file

@ -1,8 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "test-dist"
},
"include": ["src/test"],
"exclude": ["node_modules"]
}

View file

@ -1,120 +0,0 @@
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
fenix = {
url = "github:nix-community/fenix";
inputs.nixpkgs.follows = "nixpkgs";
};
rust-manifest = {
url = "https://static.rust-lang.org/dist/channel-rust-1.88.0.toml";
flake = false;
};
};
outputs = inputs @ { self, flake-parts, nixpkgs, fenix, rust-manifest, }:
flake-parts.lib.mkFlake { inherit inputs; } {
systems = [ "x86_64-linux" ];
perSystem = {config, lib, pkgs, system, ...}:
let
rust-toolchain = (fenix.packages.${system}.fromManifestFile rust-manifest).defaultToolchain;
tinymist = (pkgs.makeRustPlatform {
cargo = rust-toolchain;
rustc = rust-toolchain;
}).buildRustPackage (finalAttrs: {
pname = "tinymist";
# Please update the corresponding vscode extension when updating
# this derivation.
version = "0.14.6-rc1";
src = pkgs.lib.cleanSource ../../..;
useFetchCargoVendor = true;
cargoHash = "sha256-IyGYBbb8ilK+8fsFAm1N2A0Cw0qrbTqG20TgQs+1yaA=";
nativeBuildInputs = [
pkgs.installShellFiles
pkgs.pkg-config
];
checkFlags = [
"--skip=e2e"
# Require internet access
"--skip=docs::package::tests::cetz"
"--skip=docs::package::tests::fletcher"
"--skip=docs::package::tests::tidy"
"--skip=docs::package::tests::touying"
# Tests are flaky for unclear reasons since the 0.12.3 release
# Reported upstream: https://github.com/Myriad-Dreamin/tinymist/issues/868
"--skip=analysis::expr_tests::scope"
"--skip=analysis::post_type_check_tests::test"
"--skip=analysis::type_check_tests::test"
"--skip=completion::tests::test_pkgs"
"--skip=folding_range::tests::test"
"--skip=goto_definition::tests::test"
"--skip=hover::tests::test"
"--skip=inlay_hint::tests::smart"
"--skip=prepare_rename::tests::prepare"
"--skip=references::tests::test"
"--skip=rename::tests::test"
"--skip=semantic_tokens_full::tests::test"
];
postInstall = lib.optionalString (pkgs.stdenv.hostPlatform.emulatorAvailable pkgs.buildPackages) (
let
emulator = pkgs.stdenv.hostPlatform.emulator pkgs.buildPackages;
in
''
installShellCompletion --cmd tinymist \
--bash <(${emulator} $out/bin/tinymist completion bash) \
--fish <(${emulator} $out/bin/tinymist completion fish) \
--zsh <(${emulator} $out/bin/tinymist completion zsh)
''
);
nativeInstallCheckInputs = [
pkgs.versionCheckHook
];
versionCheckProgramArg = "-V";
doInstallCheck = true;
meta = {
description = "Tinymist is an integrated language service for Typst";
homepage = "https://github.com/Myriad-Dreamin/tinymist";
changelog = "https://github.com/Myriad-Dreamin/tinymist/blob/v${finalAttrs.version}/editors/vscode/CHANGELOG.md";
license = lib.licenses.asl20;
mainProgram = "tinymist";
maintainers = with lib.maintainers; [
GaetanLepage
lampros
];
};
});
in {
# export the project devshell as the default devshell
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
rust-analyzer
nodejs_24
(yarn.override { nodejs = nodejs_24; })
];
shellHook = ''
echo "Docs: docs/tinymist/nix.typ."
'';
};
# Developing neovim integration requires a fresh tinymist binary
devShells.neovim = pkgs.mkShell {
buildInputs = [
tinymist
];
shellHook = ''
echo "Docs: docs/tinymist/nix.typ."
'';
};
};
};
}

View file

@ -1,23 +0,0 @@
# https://wiki.nixos.org/wiki/Flakes
{
description = "A flake configuration to use tinymist CLI from unstable nixpkgs";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
};
outputs = { self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
in {
devShells.x86_64-linux.default = pkgs.mkShell {
buildInputs = with pkgs; [
tinymist
];
shellHook = ''
echo "Got tinymist from nixpkgs unstable channel"
'';
};
};
}

View file

@ -1,5 +0,0 @@
out
dist
node_modules
.vscode-test/
*.vsix

View file

@ -1,11 +0,0 @@
node_modules
.vscode/**
.vscode-test/**
src/**
.gitignore
.yarnrc
vsc-extension-quickstart.md
**/tsconfig.json
**/.eslintrc.json
**/*.map
**/*.ts

View file

@ -1 +0,0 @@
--ignore-engines true

View file

@ -1,368 +0,0 @@
# Change Log
Mirroring from [Tinymist Changelog](https://github.com/Myriad-Dreamin/tinymist/blob/main/editors/vscode/CHANGELOG.md)
## v0.11.14 - [2024-07-07]
## Compiler
This bug is introduced by [Preparing for parallelizing lsp requests](https://github.com/Myriad-Dreamin/tinymist/pull/342).
* (Fix) Lsp should respond errors at tail in https://github.com/Myriad-Dreamin/tinymist/pull/367
### Commands/Tools
* Supported single-task preview commands in https://github.com/Myriad-Dreamin/tinymist/pull/364, https://github.com/Myriad-Dreamin/tinymist/pull/368, https://github.com/Myriad-Dreamin/tinymist/pull/370, and https://github.com/Myriad-Dreamin/tinymist/pull/371
* Typst Preview extension is already integrated into Tinymist. It . Please disable Typst Preview extension to avoid conflicts.
* Otherwise, you should disable the tinymist's embedded preview feature by `"tinymist.preview": "disable"` in your settings.json.
### Preview
* Persisting webview preview through vscode restarts by @Myriad-Dreamin and @noamzaks in https://github.com/Myriad-Dreamin/tinymist/pull/373
**Full Changelog**: https://github.com/Myriad-Dreamin/tinymist/compare/v0.11.13...v0.11.14
# Change Log (v0.1.0 - v0.11.7)
All notable changes to the "typst-preview" extension will be documented in this file.
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
## v0.11.7 - [2024-06-09]
Thanks @7sDream for this release!
- Add supports for setting `sys.inputs` in configuration
- Add support for ignoring system fonts.
## v0.11.6 - [2024-05-19]
- Add extension icon designed by Zoknatwrd and QuarticCat🔮
## v0.11.5 - [2024-05-19]
- Bump to typst v0.11.1
- Show activity bar icon only when current file is a typst file
## v0.11.4 - [2024-04-09]
- Fix version in nix build
- Fix desync in firefox
## v0.11.3 - [2024-03-25]
- Bump to typst.ts 0.5.0-rc1
## v0.11.2 - [2024-03-21]
- Fix:
- #254 Zoom regression in 0.10.8 is now fixed
- #270 Wrong webview panel location when using 2-row layout
- Fix preview button being slow when using tinymist
## v0.11.1 - [2024-03-18]
- Fix:
- remove windows-ia32. It is not supported by vscode anymore.
## v0.11.0 - [2024-03-18]
- Features:
- Upgrade to typst v0.11.0
- typst-preview is available on crate.io now. You can install it by running `cargo install typst-preview`. You can also use it as a library in your project by adding `typst-preview` to your `Cargo.toml`.
## v0.10.10 - [2024-03-13]
- Features:
- Upgrade to typst v0.11.0-rc1(master, 48820fe69b8061bd949847afc343bf160d05c924)
- Bug fixes:
- Fix gradient color being rendered incorrectly
## v0.10.9 - [2024-03-10]
- Features:
- Upgrade to typst v0.11.0-rc1
- Bug fixes:
- May fix a bug when typst preview cannot launch on some windows machines
- Fix jumping view while zooming
- Fix cannot use relative path in `typst-preview.fontPaths`
## ~~v0.11.0-rc1 - [2024-03-10]~~
- Features:
- Upgrade to typst v0.11.0-rc1
- Bug fixes:
- May fix a bug when typst preview cannot launch on some windows machines
- Fix jumping view while zooming
- Fix cannot use relative path in `typst-preview.fontPaths`
## v0.10.8 - [2024-02-19]
- Features:
- Add favicon when opening the preview in browser (#239)
- Add drag to scroll. You can now drag the preview panel to scroll.
- Bug fixes:
- fix sensitive scale on touchpad (#244)
- The vscode extension will check the server version before starting.
- Misc:
- Add async tracing and add a new command `typst-preview.showAwaitTree` to pop a message and copy the async tree to clipboard. This is useful for debugging.
- Add split debug symbol for the server.
-
## v0.10.7 - [2024-01-25]
- Features:
- Jump to source is more accurate now.
- Add a config to invert color in preview panel. See `typst-preview.invertColors`.
- Allow config scroll sync mode. See `typst-preview.scrollSync`
- (Experimental) Improve cursor indicator.
## v0.10.6 - [2024-01-17]
- Bug fixes:
- fix a bug which cause the preview panel no longer updates as you type
## v0.10.5 - [2024-01-14]
- Bug fixes:
- fix a bug that fails to incrementally rendering pages with transformed content
- fix #141: glyph data desync problem, corrupting state of webview typically after your editor hibernating and coming back.
- Features:
- performance is now improved even further. We now use a more efficient way to render the document.
## v0.10.4 - [2024-01-05]
- Bug fixes:
- Fix open in browser. It's broken in v0.10.3.
- Features:
- Improve incremental rendering performance.
## v0.10.3 - [2024-01-01]
- Bug fixes:
- Thanks to new rendering technique, scrolling in no longer laggy on long document.
- Features:
- We now automatically declare the previewing file as entrypoint when `typst-preview.pinPreviewFile` is set to `true`. This is like the eye icon in webapp. This should improve diagnostic messages for typst lsp. You can enable this by setting `typst-preview.pinPreviewFile` to `true`.
## v0.10.2 - [2023-12-18]
- Bug fixes:
- fix scrollbar hiding
## v0.10.1 - [2023-12-17]
- Features:
- Improve thumbnail side panel and outline. Now it is clickable and you can jump to the corresponding page.
- Bug fixes:
- Improve performance for outline generation.
## v0.10.0 - [2023-12-05]
- Features:
- Bump to typst v0.10.0
## v0.9.2 - [2023-11-23]
- Features:
- You can now enable a preview panel in the sidebar. See `typst-preview.showInActivityBar`.
- A new keybinding is added. You can trigger preview by using `Ctrl`/`Cmd` + `k` `v` now.
- Bug fix:
- Scroll to cursor on 2-column documents is now improved.
## v0.9.1 - [2023-11-17]
- Features:
- #160: Slides mode is available now! You can enable use `typst-preview.preview-slide` command.
- Allow adjust the status bar item
- Bug fixes:
- Previously the `Compiling` status is never sent to the status bar item. This is now fixed.
- #183 #128 Various rendering fix.
## v0.9.0 - [2023-10-31]
- Features:
- Update to typst v0.9.0
- Add a status indicator in status bar. When compile fails, it becomes red. Clicking on it will show the error message.
- Bug fixes:
- #143 Scrolling is not that laggy now
- #159 Fix a clip path bug
## v0.8.3 - [2023-10-28]
- Bug fixes:
- #152 Do not pop up error message when the preview window is closed
- #156 Fix shaking scrollbar/border
- #161 #151 Should not panic when the file is not exist
- Features:
- #157 Add a rough indicator for the current cursor position in the preview panel. You may enable this in configuration.
## v0.8.2 - [2023-10-20]
- Features:
- #142 The scroll position of the preview panel is now preserved when you switch between tabs.
- #133 We now provide a button to show log when the server crashes. This should make debugging easier. You may also use the command `typst-preview.showLog` to show the log.
- #129 A `--version` flag is now provided in the cli
- Bug fixes:
- #137 Previously preview page might go blank when saving the file
- #130 Previously you cannot watch a file in `/tmp`
- #118 Previously the preview page might flash when you save the file
## v0.8.1 - [2023-09-24]
- Bug fixes:
- #121: Disable darkreader for preview panel. This should fix the problem where the preview panel is invisible when darkreader is installed in the browser.
- #123: Fix a VDOM bug which may cause color/clip-path desync.
- #124: Fix a race condition which may cause the webview process messages out of order, resulting in blank screen.
- #125: Resizing the preview panel is not that laggy now.
- Features:
- #120: We now show page breaks and center pages horizontally. By default we will choose the `vscode-sideBar-background` color as the page break color. If it is not distinguishable from white, we will use rgb(82, 86, 89) instead.
## v0.8.0 - [2023-09-17]
- Upgrade to typst v0.8.0
- Fix #111: Previously stroke related attributes are not rendered correctly. This is now fixed.
- Fix #105: The compiler will panic randomly. This is now fixed.
- Upstream bug fixes: <https://github.com/Myriad-Dreamin/typst.ts/releases/tag/v0.4.0-rc3>
## v0.7.5 - [2023-09-01]
- Fix #107: now VSCode variables like `${workspaceFolder}` can be used in `typst-preview.fontPaths`.
- Fix cannot open multiple preview tabs at the same time.
## v0.7.4 - [2023-08-29]
- Typst Preview Book is now available at <https://enter-tainer.github.io/typst-preview/> ! You can find the documentation of Typst Preview there.
- Improved standalone usage: Use `typst-preview` without VSCode now becomes easier. All you need is `typst-preview --partial-rendering cool-doc.typ`. Take a look at <https://enter-tainer.github.io/typst-preview/standalone.html>
- Upgrade to typst.ts 0.4.0-rc2. This fixes a subtle incremental parsing bug.
- Partial rendering is now enabled by default. This should improve performance on long document. You can disable it by setting `typst-preview.partialRendering` to `false`.
## v0.7.3 - [2023-08-20]
- Bugfix: fix a subtle rendering issue, [typst.ts#306](https://github.com/Myriad-Dreamin/typst.ts/pull/306).
## v0.7.2 - [2023-08-20]
- Bug fixes:
- #79: We now put typst compiler and renderer in a dedicate thread. Therefore we should get more stable performance.
- #78: Currently only the latest compile/render request is processed. This should fix the problem where the preview request will queue up when you type too fast and the doc takes a lot of time to compile.
- #81: We now use a more robust way to detect the whether to kill stale server process. This should fix the problem where the when preview tab will become blank when it becomes inactive for a while.
- #87: Add enum description for `typst-preview.scrollSync`. Previously the description is missing.
## v0.7.1 - [2023-08-16]
- Bug fixes:
- fix #41. It is now possible to use Typst Preview in VSCode Remote.
- fix #82. You can have preview button even when typst-lsp is not installed.
- Misc: We downgrade the ci image for Linux to Ubuntu 20.04. This should fix the problem where the extension cannot be installed on some old Linux distros.
## v0.7.0 - [2023-08-09]
- Upgrade to typst v0.7.0
- Bug fixes
- #77 #75: Previously arm64 devices will see a blank preview. This is now fixed.
- #74: Previously when you open a file without opening in folder, the preview will not work. This is now fixed.
## v0.6.4 - [2023-08-06]
- Rename to Typst Preview.
- Add page level partial rendering. This should improve performance on long document. This is an experimental feature and is disabled by default. You can enable it by setting `typst-preview.partialRendering` to `true`.
- The binary `typst-preview` now can be used as a standalone typst server. You can use it to preview your document in browser. For example: `typst-preview ./assets/demo/main.typ --open-in-browser --partial-rendering`
- Fix #70: now you can launch many preview instances at the same time.
## v0.6.3 - [2023-07-29]
- Fix #13, #63: Now ctrl+wheel zoom should zoom the content to the cursor position. And when the cursor is not within the document, the zoom sill works.
## v0.6.2 - [2023-07-25]
- Fix #60 and #24. Now we watch dirty files in memory therefore no shadow file is needed. Due to the removal of disk read/write, this should also improve performance and latency.
- Preview on type is now enabled by default for new users. Existing users will not be affected.
## v0.6.1 - [2023-07-14]
- Fix empty file preview. Previously, if you start with an empty file and type something, the preview will not be updated. This is now fixed.
## v0.6.0 - [2023-07-06]
- Upgrade to typst v0.6.0
- Bug fixes:
- #48: Webview cannot load frontend resources when VSCode is installed by scoop
- #46: Preview to source jump not working after inserting new text in the source file
- #52: Bug fix about VDOM operation
- Enhancement
- #54: Only scroll the preview panel when the event is triggered by mouse
## v0.5.1 - [2023-06-30]
- Performance improvement(#14): We now use typst.ts. We utilize a [virtual DOM](https://en.wikipedia.org/wiki/Virtual_DOM) approach to diff and render the document. This is a **significant enhancement** of previewing document in `onType` mode in terms of resource savings and response time for changes.
- Cross jump between code and preview (#36): We implement SyncTeX-like feature for typst-preview. You can now click on the preview panel to jump to the corresponding code location, and vice versa. This feature is still experimental and may not work well in some cases. Please report any issues you encounter.
- Sync preview position with cursor: We now automatically scroll the preview panel to the corresponding position of the cursor. This feature is controlled by `typst-preview.scrollSync`
- Open preview in separate window(#39): You can type `typst-preview.browser` in command palette to open the preview page in a separate browser.
- Links in preview panel: You can now click on links in the preview panel to open them in browser. The cross reference links are also clickable.
- Text selection in preview panel: You can now select text in the preview panel.
## v0.5.0 - [2023-06-10]
- Upgrade to typst v0.5.0
## v0.4.1 - [2023-06-07]
- Makes the WebSocket connection retry itself when it is closed, with a delay of 1 second.
## v0.4.0 - [2023-06-07]
- Upgrade to typst v0.4.0
## v0.3.3 - [2023-05-11]
- Fix nix-ld compatibility by inheriting env vars(#33)
## v0.3.1 - [2023-05-04]
- Publish to OpenVSX
- allow configuring font paths
## v0.3.0 - [2023-04-26]
- Upgrade typst to v0.3.0
- Fix panic when pages are removed
## v0.2.4 - [2023-04-21]
- Automatically choose a free port to listen. This should fix the problem where you can't preview multiple files at the same time.
- Server will exit right after client disconnects, preventing resource leak.
## v0.2.3 - [2023-04-20]
- Performance Improvement: only update pages when they are visible. This should improve performance when you have a lot of pages.
## v0.2.2 - [2023-04-16]
- Fix server process not killed on exit(maybe)
- Add config for OnSave/OnType
- Add output channel for logging
## v0.2.1 - [2023-04-16]
- Bundle typst-ws within vsix. You no longer need to install typst-ws
## v0.1.7 - [2023-04-10]
- Preview on type
- Add config entry for `typst-ws` path
## v0.1.6 - [2023-04-09]
Add preview button
## v0.1.0 - [2023-04-09]
Initial release

View file

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2023 mgt
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,55 +0,0 @@
# [DEPRECATION NOTICE] Typst preview extension has been integrated into [tinymist](https://github.com/Myriad-Dreamin/tinymist)
We recommend all users migrate to tinymist for the following benefits:
- More centralized resource management
- Reduced redundancy and lower resource usage
- Easier updates and maintenance
This repository will no longer be updated in future. All development will move to tinymist. Thank you for your support and understanding!
- We still maintain the typst-preview extension for a while in a best effort way.
- The lazy people can continue using their setting, as all old things are still working.
- This respect people who love minimal env, like a treesitter plugin plus preview.
- Tinymist will ensure compatibility to typst-preview as much as possible.
- for vscode users: uninstall the preview extension and install the tinymist extension.
- for standalone cli users: `typst-preview -> tinymist preview`
# [Typst Preview VSCode](https://github.com/Enter-tainer/typst-preview)
Preview your Typst files in vscode instantly!
## Features
- Low latency preview: preview your document instantly on type. The incremental rendering technique makes the preview latency as low as possible.
- Open in browser: open the preview in browser, so you put it in another monitor. https://github.com/typst/typst/issues/1344
- Cross jump between code and preview: We implement SyncTeX-like feature for typst-preview. You can now click on the preview panel to jump to the corresponding code location, and vice versa.
Install this extension from [marketplace](https://marketplace.visualstudio.com/items?itemName=mgt19937.typst-preview), open command palette (Ctrl+Shift+P), and type `>Typst Preview:`.
You can also use the shortcut (Ctrl+K V).
![demo](demo.png)
For more information, please visit documentation at [Typst Preview Book](https://enter-tainer.github.io/typst-preview/).
## Extension Settings
See https://enter-tainer.github.io/typst-preview/config.html
## Bug report
To achieve high performance instant preview, we use a **different rendering backend** from official typst. We are making our best effort to keep the rendering result consistent with official typst. We have set up comprehensive tests to ensure the consistency of the rendering result. But we cannot guarantee that the rendering result is the same in all cases. There can be unknown corner cases that we haven't covered.
**Therefore, if you encounter any rendering issue, please report it to this repo other than official typst repo.**
## Known Issues
See [issues](https://github.com/Enter-tainer/typst-preview/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc) on GitHub.
## Legal
This project is not affiliated with, created by, or endorsed by Typst the brand.
## Change Log
See [CHANGELOG.md](CHANGELOG.md)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

View file

@ -1,282 +0,0 @@
{
"name": "typst-preview",
"displayName": "Typst Preview",
"description": "preview your typst document in instant",
"publisher": "mgt19937",
"license": "MIT",
"icon": "./preview-logo.png",
"repository": {
"type": "git",
"url": "https://github.com/Enter-tainer/typst-preview"
},
"version": "0.11.14",
"engines": {
"vscode": "^1.97.0"
},
"categories": [
"Other"
],
"activationEvents": [
"onLanguage:typst"
],
"main": "./out/extension.js",
"contributes": {
"icons": {
"typst-guy": {
"description": "typst guy",
"default": {
"fontPath": "./preview.woff",
"fontCharacter": "A"
}
}
},
"languages": [
{
"id": "typst",
"extensions": [
".typ"
]
}
],
"commands": [
{
"command": "typst-preview.preview",
"title": "Typst Preview: Preview current file",
"description": "Launch typst-preview server",
"icon": "$(open-preview)",
"when": "resourceLangId == typst && editorTextFocus"
},
{
"command": "typst-preview.browser",
"title": "Typst Preview: Preview current file in browser",
"description": "Launch typst-preview server and open the preview in your browser",
"icon": "$(open-preview)",
"when": "resourceLangId == typst && editorTextFocus"
},
{
"command": "typst-preview.preview-slide",
"title": "Typst Preview: Preview current file in slide mode",
"description": "Launch typst-preview server in slide mode",
"icon": "$(open-preview)",
"when": "resourceLangId == typst && editorTextFocus"
},
{
"command": "typst-preview.browser-slide",
"title": "Typst Preview: Preview current file in browser and slide mode",
"description": "Launch typst-preview server in slide mode and open the preview in your browser",
"icon": "$(open-preview)",
"when": "resourceLangId == typst && editorTextFocus"
},
{
"command": "typst-preview.sync",
"title": "Typst Preview: Sync preview with current cursor",
"description": "Scroll preview to current cursor position",
"icon": "$(sync)",
"when": "resourceLangId == typst && editorTextFocus"
},
{
"command": "typst-preview.showLog",
"title": "Typst Preview: Show Log",
"description": "Show typst-preview log",
"icon": "$(list-flat)",
"when": "resourceLangId == typst"
},
{
"command": "typst-preview.noteOutline",
"title": "Note: Jumping to source location of the outline item doesn't work well if its body doesn't have source location, e.g.\n```\n#let my-heading(h) = heading(h) // will jump to here\n#my-heading(\"Title\") // will not jump to here\n```.\nHence, you may want to use `my-heading[Title]` instead to gain better experience of outline jumping.",
"description": "...",
"icon": "$(extensions-info-message)"
}
],
"menus": {
"editor/title": [
{
"command": "typst-preview.preview",
"when": "resourceLangId == typst && editorTextFocus",
"group": "navigation"
}
],
"view/title": [
{
"command": "typst-preview.noteOutline",
"when": "view == typst-preview.outline",
"group": "navigation"
}
]
},
"viewsContainers": {
"activitybar": [
{
"id": "typst-preview-activitybar",
"title": "Typst Preview",
"icon": "./icons/activity-bar.svg"
}
]
},
"views": {
"typst-preview-activitybar": [
{
"id": "typst-preview.content-preview",
"type": "webview",
"name": "Content",
"when": "config.typst-preview.showInActivityBar && resourceLangId == typst"
},
{
"id": "typst-preview.outline",
"name": "Outline",
"when": "config.typst-preview.showInActivityBar && resourceLangId == typst"
}
]
},
"configuration": {
"title": "Typst Preview",
"properties": {
"typst-preview.executable": {
"type": "string",
"default": "",
"description": "Path to typst-preview executable. If not set, the extension will use bundled typst-preview. If bundled binary is not found, it will use typst-preview installed in PATH"
},
"typst-preview.sysInputs": {
"type": "object",
"items": {
"type": "string"
},
"default": {},
"description": "key-value pairs visible through `sys.inputs`, corresponds to `--input` argument of typst cli"
},
"typst-preview.systemFonts": {
"type": "boolean",
"default": true,
"description": "Whether to load system fonts. If disabled, only fonts in `typst-preview.fontPaths` is loaded"
},
"typst-preview.fontPaths": {
"type": "array",
"items": {
"type": "string",
"title": "Font path",
"description": "Absolute path to a directory or file containing font assets."
},
"default": [],
"description": "List of *additional* paths to font assets used by typst-preview."
},
"typst-preview.refresh": {
"title": "Refresh preview",
"description": "Refresh preview when the document is saved or when the document is changed",
"type": "string",
"enum": [
"onSave",
"onType"
],
"default": "onType",
"enumDescriptions": [
"Refresh preview on save",
"Refresh preview on type"
]
},
"typst-preview.scrollSync": {
"description": "Configure scroll sync mode.",
"type": "string",
"enum": [
"never",
"onSelectionChangeByMouse",
"onSelectionChange"
],
"default": "onSelectionChangeByMouse",
"enumDescriptions": [
"Disable automatic scroll sync",
"Scroll preview to current cursor position when selection changes by mouse",
"Scroll preview to current cursor position when selection changes by mouse or keyboard (any source)"
]
},
"typst-preview.partialRendering": {
"description": "Only render visible part of the document. This can improve performance but still being experimental.",
"type": "boolean",
"default": true
},
"typst-preview.invertColors": {
"description": "Invert colors of the preview (useful for dark themes without cost). Please note you could see the origin colors when you hover elements in the preview.",
"type": "string",
"enum": [
"never",
"auto",
"always"
],
"default": "never",
"enumDescriptions": [
"Disable color inversion of the preview",
"Invert colors smartly by detecting dark/light themes in browser environment or by `typst query` your document",
"Always invert colors of the preview"
]
},
"typst-preview.cursorIndicator": {
"description": "(Experimental) Show typst cursor indicator in preview.",
"type": "boolean",
"default": false
},
"typst-preview.showInActivityBar": {
"description": "(Experimental) Show a preview panel in activity bar.",
"type": "boolean",
"default": false
},
"typst-preview.statusBarIndicator": {
"description": "Style of status bar indicator for typst-preview.",
"type": "string",
"enum": [
"full",
"compact"
],
"default": "compact",
"enumDescriptions": [
"Show full status bar indicator (ICON Compile Success)",
"Show compact status bar indicator (ICON)"
]
},
"typst-preview.pinPreviewFile": {
"description": "Declare current previewing file as entrypoint for typst-lsp. This will make typst-lsp to use this file as entrypoint instead of the file opened in vscode. This can improve diagnostics messages and auto completion but still being experimental.",
"type": "boolean",
"default": false
}
}
},
"keybindings": [
{
"command": "typst-preview.preview",
"key": "ctrl+k v",
"mac": "cmd+k v",
"when": "editorLangId == typst"
}
]
},
"scripts": {
"build:frontend": "cd ../../../../ && yarn build:preview && cd contrib/typst-preview/editors/vscode && yarn install:frontend",
"install:frontend": "rimraf ./out/frontend/ && cpr ../../../../tools/typst-preview-frontend/dist/ ./out/frontend/",
"build-base": "esbuild ../../../../editors/vscode/src/extension.preview.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node --target=node16",
"vscode:prepublish": "yarn run build-base -- --minify && yarn run build:frontend",
"compile": "yarn run build-base -- --sourcemap && yarn run build:frontend",
"build": "yarn run compile",
"watch": "yarn run build-base -- --sourcemap --watch",
"pretest": "yarn run compile && yarn run lint",
"package": "vsce package --yarn",
"lint": "eslint src",
"test": "node ./out/test/runTest.js"
},
"devDependencies": {
"@types/cross-spawn": "^6.0.2",
"@types/glob": "^8.1.0",
"@types/mocha": "^10.0.1",
"@types/node": "^22.13.4",
"@types/vscode": "^1.97.0",
"@types/ws": "^8.5.5",
"@vscode/test-electron": "^2.3.9",
"@vscode/vsce": "^2.22.0",
"glob": "^8.1.0",
"mocha": "^10.2.0",
"typescript": "^5.2.2"
},
"dependencies": {
"cross-spawn": "^7.0.3",
"node-fetch": "^3.3.2",
"vscode-variables": "^0.1.3",
"ws": "^8.13.0"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

View file

@ -1,22 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2" viewBox="0 0 600 600">
<filter xmlns="http://www.w3.org/2000/svg" id="a" width="100%" height="100%" x="0%" y="0%">
<feTurbulence xmlns="http://www.w3.org/2000/svg" baseFrequency=".005" type="fractalNoise"/>
</filter>
<path d="M0 0h600v600H0z" style="fill:#30a9b3;fill-rule:nonzero"/>
<path d="M0 0h600v600H0z" opacity=".5" style="filter:url(#a)"/>
<path d="M7.888 18.148c0 1.133.162 1.892.487 2.28.325.387.916.581 1.773.581.886 0 2.024-.447 3.412-1.341l.886 1.475c-2.599 2.175-4.741 3.263-6.425 3.263-1.684 0-3.014-.402-3.988-1.207-.975-.834-1.463-2.294-1.463-4.38V7.107H.355L0 5.453l2.57-.804V2.458L7.888 0v4.917l5.229-.402-.488 2.905-4.741-.179v10.907Z" style="fill:#fff;fill-rule:nonzero" transform="translate(200 122) scale(14.1524)"/>
<circle cx="240" cy="247" r="101" style="fill:#30a9b3" transform="translate(-7 9)"/>
<g transform="translate(163 144) scale(16.7338)">
<clipPath id="b">
<circle cx="4.183" cy="6.693" r="6.036"/>
</clipPath>
<g clip-path="url(#b)">
<path d="M7.888 18.148c0 1.133.162 1.892.487 2.28.325.387.916.581 1.773.581.886 0 2.024-.447 3.412-1.341l.886 1.475c-2.599 2.175-4.741 3.263-6.425 3.263-1.684 0-3.014-.402-3.988-1.207-.975-.834-1.463-2.294-1.463-4.38V7.107H.355L0 5.453l2.57-.804V2.458L7.888 0v4.917l5.229-.402-.488 2.905-4.741-.179v10.907Z" style="fill:#fff;fill-rule:nonzero"/>
</g>
</g>
<path d="M352.734 406.179a8.004 8.004 0 0 1-5.668-2.348l-72.685-72.685a8.019 8.019 0 0 1 0-11.337 8.018 8.018 0 0 1 11.337 0l72.685 72.685a8.018 8.018 0 0 1-5.669 13.685" style="fill:#7b7979;fill-rule:nonzero" transform="translate(30)"/>
<path d="m429.598 441.015-59.858-59.858a23.91 23.91 0 0 0-17.006-7.045 23.91 23.91 0 0 0-17.006 7.045c-9.377 9.376-9.377 24.635 0 34.011l59.858 59.858a23.91 23.91 0 0 0 17.006 7.045 23.905 23.905 0 0 0 17.006-7.044c9.377-9.377 9.377-24.636 0-34.012" style="fill:#a7a6a6;fill-rule:nonzero" transform="translate(30)"/>
<path d="M330.327 389.472c-3.325 8.611-1.534 18.76 5.402 25.696l59.858 59.858a23.91 23.91 0 0 0 17.006 7.045c3.026 0 5.958-.574 8.696-1.635l-90.962-90.964Z" style="fill:#918f90;fill-rule:nonzero" transform="translate(30)"/>
<path d="M203.089 129.336c70.433 0 127.732 57.301 127.732 127.733s-57.3 127.732-127.732 127.732S75.356 327.502 75.356 257.069c0-70.433 57.301-127.733 127.733-127.733Zm0 34.205c-51.572 0-93.528 41.956-93.528 93.528s41.957 93.528 93.528 93.528c51.571 0 93.528-41.956 93.528-93.528s-41.956-93.528-93.528-93.528Z" style="fill:#ff8c78" transform="translate(30)"/>
<path d="M203.089 149.645c59.234 0 107.424 48.19 107.424 107.424 0 59.234-48.19 107.424-107.424 107.424-59.234 0-107.424-48.19-107.424-107.424 0-59.234 48.19-107.424 107.424-107.424Zm0 13.896c-51.572 0-93.528 41.956-93.528 93.528s41.957 93.528 93.528 93.528c51.571 0 93.528-41.956 93.528-93.528s-41.956-93.528-93.528-93.528Z" style="fill:#df7a6e" transform="translate(30)"/>
</svg>

Before

Width:  |  Height:  |  Size: 3 KiB

View file

@ -1,2 +0,0 @@
interface Window {}
declare const acquireVsCodeApi: any;

View file

@ -1,19 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "ES2020",
"outDir": "out",
"lib": ["ES2020", "DOM"],
"sourceMap": true,
"rootDir": "../../../../editors/vscode",
"skipLibCheck": true,
"types": ["./src/global.d.ts"],
"resolveJsonModule": true
/* Additional Checks */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
},
"files": ["../../../../editors/vscode/src/extension.preview.ts"],
"include": ["../../../../editors/vscode/src/**/*.ts"]
}

View file

@ -1,19 +0,0 @@
[package]
name = "cmark-writer-macros"
version = "0.9.0"
edition = "2021"
authors = ["Hong Jiarong"]
description = "Proc-macro implementations for cmark-writer"
license = "MIT"
[lib]
proc-macro = true
[dependencies]
syn = { version = "2.0", features = ["full", "extra-traits"] }
quote = "1.0"
proc-macro2 = "1.0"
[dev-dependencies]
cmark-writer = { path = "../cmark-writer" }
ecow = "0.2.6"

View file

@ -1,9 +0,0 @@
MIT License
Copyright 2025 Hong Jiarong
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,387 +0,0 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{
parse::Parse, parse::ParseStream, parse_macro_input, DeriveInput, Ident, LitBool, LitStr, Token,
};
/// Parse custom_node attribute parameters
struct CustomNodeArgs {
is_block: Option<bool>,
html_impl: Option<bool>,
}
impl Parse for CustomNodeArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut is_block = None;
let mut html_impl = None;
if input.is_empty() {
return Ok(CustomNodeArgs {
is_block,
html_impl,
});
}
loop {
if input.is_empty() {
break;
}
let ident: Ident = input.parse()?;
if ident == "block" {
let _: Token![=] = input.parse()?;
let value: LitBool = input.parse()?;
is_block = Some(value.value);
} else if ident == "html_impl" {
let _: Token![=] = input.parse()?;
let value: LitBool = input.parse()?;
html_impl = Some(value.value);
} else {
return Err(syn::Error::new_spanned(
ident,
"Unknown attribute parameter",
));
}
// Handle optional comma separator
if input.peek(Token![,]) {
let _: Token![,] = input.parse()?;
}
}
Ok(CustomNodeArgs {
is_block,
html_impl,
})
}
}
#[derive(Default)]
struct StructureErrorArgs {
format: Option<LitStr>,
}
impl Parse for StructureErrorArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut format = None;
while !input.is_empty() {
let ident: Ident = input.parse()?;
if ident == "format" {
let _: Token![=] = input.parse()?;
let value: LitStr = input.parse()?;
format = Some(value);
} else {
return Err(syn::Error::new_spanned(
ident,
"Unknown attribute parameter",
));
}
if input.peek(Token![,]) {
let _: Token![,] = input.parse()?;
} else {
break;
}
}
Ok(Self { format })
}
}
/// Custom node attribute macro for implementing the CustomNode trait
///
/// This macro automatically implements the CustomNode trait. Users can specify
/// whether the node is a block element using the `block` parameter and whether
/// it implements HTML rendering with the `html_impl` parameter.
///
/// # Example
///
/// ```rust
/// use cmark_writer::writer::{BlockWriterProxy, HtmlWriteResult, HtmlWriter, InlineWriterProxy};
/// use cmark_writer::{custom_node, WriteResult};
/// use ecow::EcoString;
///
/// // Specified as an inline element with both CommonMark and HTML implementations
/// #[derive(Debug, Clone, PartialEq)]
/// #[custom_node(block=false, html_impl=true)]
/// struct HighlightNode {
/// content: EcoString,
/// color: EcoString,
/// }
///
/// impl HighlightNode {
/// // Required for CommonMark rendering
/// fn write_custom(&self, writer: &mut InlineWriterProxy) -> WriteResult<()> {
/// writer.write_str("<span style=\"background-color: ")?;
/// writer.write_str(&self.color)?;
/// writer.write_str("\">")?;
/// writer.write_str(&self.content)?;
/// writer.write_str("</span>")?;
/// Ok(())
/// }
///
/// // Optional HTML rendering implementation
/// fn write_html_custom(&self, writer: &mut HtmlWriter) -> HtmlWriteResult<()> {
/// writer.start_tag("span")?;
/// writer.attribute("style", &format!("background-color: {}", self.color))?;
/// writer.finish_tag()?;
/// writer.text(&self.content)?;
/// writer.end_tag("span")?;
/// Ok(())
/// }
/// }
///
/// // Only CommonMark implementation, default HTML implementation
/// #[derive(Debug, Clone, PartialEq)]
/// #[custom_node(block=true)]
/// struct AlertNode {
/// content: EcoString,
/// }
///
/// impl AlertNode {
/// fn write_custom(&self, writer: &mut BlockWriterProxy) -> WriteResult<()> {
/// writer.write_str("<div class=\"alert\">")?;
/// writer.write_str(&self.content)?;
/// writer.write_str("</div>")?;
/// Ok(())
/// }
/// }
/// ```
#[proc_macro_attribute]
pub fn custom_node(attr: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(attr as CustomNodeArgs);
let input = parse_macro_input!(item as DeriveInput);
let name = &input.ident;
// Configure is_block implementation
let is_block_impl = if let Some(is_block) = args.is_block {
quote! {
fn is_block(&self) -> bool {
#is_block
}
}
} else {
quote! {
fn is_block(&self) -> bool {
self.is_block_custom()
}
}
};
let is_block = args.is_block.unwrap_or(false);
// Configure html_write implementation
let html_write_impl = if args.html_impl.unwrap_or(false) {
// When html_impl=true, expect user to implement write_html_custom method
quote! {
fn html_write(
&self,
writer: &mut ::cmark_writer::writer::HtmlWriter,
) -> ::cmark_writer::writer::HtmlWriteResult<()> {
self.write_html_custom(writer)
}
}
} else {
// When html_impl is not set or false, use default implementation
quote! {
fn html_write(
&self,
writer: &mut ::cmark_writer::writer::HtmlWriter,
) -> ::cmark_writer::writer::HtmlWriteResult<()> {
writer.write_trusted_html(&format!(
"<!-- HTML rendering not implemented for Custom Node: {} -->",
self.type_name()
))?;
Ok(())
}
}
};
let write_block_impl = if is_block {
quote! {
fn write_block(
&self,
writer: &mut ::cmark_writer::writer::BlockWriterProxy,
) -> ::cmark_writer::error::WriteResult<()> {
self.write_custom(writer)
}
}
} else {
quote! {}
};
let write_inline_impl = if !is_block {
quote! {
fn write_inline(
&self,
writer: &mut ::cmark_writer::writer::InlineWriterProxy,
) -> ::cmark_writer::error::WriteResult<()> {
self.write_custom(writer)
}
}
} else {
quote! {}
};
let expanded = quote! {
#input
impl ::cmark_writer::ast::CustomNode for #name {
#write_block_impl
#write_inline_impl
#html_write_impl
fn clone_box(&self) -> Box<dyn ::cmark_writer::ast::CustomNode> {
Box::new(self.clone())
}
fn eq_box(&self, other: &dyn ::cmark_writer::ast::CustomNode) -> bool {
if let Some(other) = other.as_any().downcast_ref::<Self>() {
self == other
} else {
false
}
}
#is_block_impl
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}
impl #name {
pub fn matches(node: &dyn ::cmark_writer::ast::CustomNode) -> bool {
node.type_name() == std::any::type_name::<#name>() ||
node.as_any().downcast_ref::<#name>().is_some()
}
pub fn extract(node: Box<dyn ::cmark_writer::ast::CustomNode>) -> Option<#name> {
node.as_any().downcast_ref::<#name>().map(|n| n.clone())
}
}
};
TokenStream::from(expanded)
}
/// Custom error attribute macro, replaces the struct form errors in the original define_custom_errors! macro
///
/// # Example
///
/// ```rust
/// use cmark_writer::structure_error;
///
/// #[structure_error(format = "Table column mismatch: {}")]
/// struct TableColumnMismatchError(pub &'static str);
/// ```
#[proc_macro_attribute]
pub fn structure_error(attr: TokenStream, item: TokenStream) -> TokenStream {
let args = if attr.is_empty() {
StructureErrorArgs::default()
} else {
parse_macro_input!(attr as StructureErrorArgs)
};
let input = parse_macro_input!(item as DeriveInput);
let name = &input.ident;
// Parse format attribute
let format_lit = args
.format
.unwrap_or_else(|| LitStr::new("{}", Span::call_site()));
let expanded = quote! {
#input
impl #name {
pub fn new(message: &'static str) -> Self {
Self(message)
}
pub fn into_error(self) -> ::cmark_writer::error::WriteError {
let mut error_factory = ::cmark_writer::error::StructureError::new(#format_lit);
let arg = self.0.to_string();
error_factory = error_factory.arg(arg);
<::cmark_writer::error::StructureError as ::cmark_writer::error::CustomErrorFactory>::create_error(&error_factory)
}
}
impl From<#name> for ::cmark_writer::error::WriteError {
fn from(factory: #name) -> Self {
factory.into_error()
}
}
impl ::cmark_writer::error::CustomErrorFactory for #name {
fn create_error(&self) -> ::cmark_writer::error::WriteError {
let mut error_factory = ::cmark_writer::error::StructureError::new(#format_lit);
let arg = self.0.to_string();
error_factory = error_factory.arg(arg);
<::cmark_writer::error::StructureError as ::cmark_writer::error::CustomErrorFactory>::create_error(&error_factory)
}
}
};
TokenStream::from(expanded)
}
/// Custom coded error attribute macro, replaces the coded form errors in the original define_custom_errors! macro
///
/// # Example
///
/// ```rust
/// use cmark_writer::coded_error;
///
/// #[coded_error]
/// struct MarkdownSyntaxError(pub String, pub String);
/// ```
#[proc_macro_attribute]
pub fn coded_error(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
let name = &input.ident;
let expanded = quote! {
#input
impl #name {
pub fn new(message: &str, code: &str) -> Self {
Self(message.to_string(), code.to_string())
}
pub fn into_error(self) -> ::cmark_writer::error::WriteError {
let coded_error = ::cmark_writer::error::CodedError::new(self.0, self.1);
<::cmark_writer::error::CodedError as ::cmark_writer::error::CustomErrorFactory>::create_error(&coded_error)
}
}
impl From<#name> for ::cmark_writer::error::WriteError {
fn from(factory: #name) -> Self {
factory.into_error()
}
}
impl ::cmark_writer::error::CustomErrorFactory for #name {
fn create_error(&self) -> ::cmark_writer::error::WriteError {
let coded_error = ::cmark_writer::error::CodedError::new(self.0.clone(), self.1.clone());
<::cmark_writer::error::CodedError as ::cmark_writer::error::CustomErrorFactory>::create_error(&coded_error)
}
}
};
TokenStream::from(expanded)
}

View file

@ -1,24 +0,0 @@
[package]
name = "cmark-writer"
version = "0.9.0"
edition = "2021"
description = "A CommonMark writer implementation in Rust for serializing AST nodes to CommonMark format"
license = "MIT"
authors = ["Hong Jiarong"]
repository = "https://github.com/hongjr03/cmark-writer"
keywords = ["markdown", "commonmark", "writer", "serializer"]
categories = ["text-processing"]
readme = "README.md"
[dependencies]
cmark-writer-macros = { path = "../cmark-writer-macros", version = "0.9.0" }
ecow = "0.2.5"
env_logger = "0.11.8"
html-escape = "0.2.13"
log = "0.4.27"
[features]
default = []
# GitHub Flavored Markdown support including tables with alignment, strikethrough,
# task lists, and autolinks without angle brackets
gfm = []

View file

@ -1,9 +0,0 @@
MIT License
Copyright 2025 Hong Jiarong
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,164 +0,0 @@
# cmark-writer
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](./LICENSE)
A CommonMark writer implementation in Rust.
## Basic Usage
```rust
use cmark_writer::ast::{Node, ListItem};
use cmark_writer::writer::CommonMarkWriter;
// Create a document
let document = Node::Document(vec![
Node::heading(1, vec![Node::Text("Hello CommonMark".into())]),
Node::Paragraph(vec![
Node::Text("This is a simple ".into()),
Node::Strong(vec![Node::Text("example".into())]),
Node::Text(".".into()),
]),
]);
// Render to CommonMark
let mut writer = CommonMarkWriter::new();
writer.write(&document).expect("Failed to write document");
let markdown = writer.into_string();
println!("{}", markdown);
```
## Custom Options
```rust
use cmark_writer::options::WriterOptionsBuilder;
use cmark_writer::writer::CommonMarkWriter;
// Use builder pattern for custom options
let options = WriterOptionsBuilder::new()
.strict(true)
.hard_break_spaces(false)
.indent_spaces(2)
.build();
let mut writer = CommonMarkWriter::with_options(options);
```
## Table Support
```rust
use cmark_writer::ast::{Node, tables::TableBuilder};
// Create tables with the builder pattern
let table = TableBuilder::new()
.headers(vec![
Node::Text("Name".into()),
Node::Text("Age".into())
])
.add_row(vec![
Node::Text("John".into()),
Node::Text("30".into()),
])
.add_row(vec![
Node::Text("Alice".into()),
Node::Text("25".into()),
])
.build();
```
## GitHub Flavored Markdown (GFM)
Enable GFM features by adding to your `Cargo.toml`:
```toml
[dependencies]
cmark-writer = { version = "0.8.0", features = ["gfm"] }
```
GFM Support:
- Tables with column alignment
- Strikethrough text
- Task lists
- Extended autolinks
- HTML element filtering
## HTML Writing
The library provides dedicated HTML writing capabilities through the `HtmlWriter` class:
```rust
use cmark_writer::{HtmlWriter, HtmlWriterOptions, Node};
// Create HTML writer with custom options
let options = HtmlWriterOptions {
strict: true,
code_block_language_class_prefix: Some("language-".into()),
#[cfg(feature = "gfm")]
enable_gfm: true,
#[cfg(feature = "gfm")]
gfm_disallowed_html_tags: vec!["script".into()],
};
let mut writer = HtmlWriter::with_options(options);
// Write some nodes
let paragraph = Node::Paragraph(vec![Node::Text("Hello HTML".into())]);
writer.write_node(&paragraph).unwrap();
// Get resulting HTML
let html = writer.into_string().unwrap();
assert_eq!(html, "<p>Hello HTML</p>\n");
```
## Custom Nodes
```rust
use cmark_writer::{HtmlWriteResult, HtmlWriter, Node, WriteResult};
use cmark_writer::writer::InlineWriterProxy;
use cmark_writer::custom_node;
use ecow::EcoString;
#[derive(Debug, Clone, PartialEq)]
#[custom_node(block=false, html_impl=true)]
struct HighlightNode {
content: EcoString,
color: EcoString,
}
impl HighlightNode {
// Implementation for CommonMark output
fn write_custom(&self, writer: &mut InlineWriterProxy) -> WriteResult<()> {
writer.write_str("<span style=\"background-color: ")?;
writer.write_str(&self.color)?;
writer.write_str("\">")?;
writer.write_str(&self.content)?;
writer.write_str("</span>")?;
Ok(())
}
// Optional HTML-specific implementation
fn write_html_custom(&self, writer: &mut HtmlWriter) -> HtmlWriteResult<()> {
writer.start_tag("span")?;
writer.attribute("style", &format!("background-color: {}", self.color))?;
writer.finish_tag()?;
writer.text(&self.content)?;
writer.end_tag("span")?;
Ok(())
}
}
```
## Development
```bash
# Build
cargo build
# Run tests
cargo test
```
## License
This project is licensed under the MIT License - see the LICENSE file for details.

View file

@ -1,135 +0,0 @@
//! Custom node definitions for the CommonMark AST.
use crate::error::{WriteError, WriteResult};
use crate::writer::{BlockWriterProxy, HtmlWriteResult, HtmlWriter, InlineWriterProxy};
use std::any::Any;
/// Trait for implementing custom node behavior for the CommonMark AST.
///
/// This trait defines methods that all custom node types must implement.
/// Users can implement dedicated block or inline rendering methods for CommonMark output and
/// optionally override the `html_write` method for HTML output.
///
/// The recommended way to implement this trait is through the `custom_node` macro,
/// which provides a default implementation of most methods and requires users to
/// implement only the node-specific logic.
///
/// # Example
///
/// ```rust
/// use ecow::EcoString;
/// use cmark_writer_macros::custom_node;
/// use cmark_writer::error::WriteResult;
/// use cmark_writer::writer::{HtmlWriteResult, HtmlWriter, InlineWriterProxy};
///
/// // Define a custom node with support for both CommonMark and HTML output
/// #[derive(Debug, Clone, PartialEq)]
/// #[custom_node(block=false, html_impl=true)]
/// struct HighlightNode {
/// content: EcoString,
/// color: EcoString,
/// }
///
/// impl HighlightNode {
/// // Required for CommonMark output
/// fn write_custom(&self, writer: &mut InlineWriterProxy) -> WriteResult<()> {
/// writer.write_str("<span style=\"background-color: ")?;
/// writer.write_str(&self.color)?;
/// writer.write_str("\">")?;
/// writer.write_str(&self.content)?;
/// writer.write_str("</span>")?;
/// Ok(())
/// }
///
/// // Optional HTML-specific implementation
/// fn write_html_custom(&self, writer: &mut HtmlWriter) -> HtmlWriteResult<()> {
/// writer.start_tag("span")?;
/// writer.attribute("style", &format!("background-color: {}", self.color))?;
/// writer.finish_tag()?;
/// writer.text(&self.content)?;
/// writer.end_tag("span")?;
/// Ok(())
/// }
/// }
/// ```
pub trait CustomNode: std::fmt::Debug + Send + Sync {
/// Write the custom node as a block element using the restricted block writer proxy.
///
/// Block custom nodes should implement this method to emit valid block-level content.
fn write_block(&self, writer: &mut BlockWriterProxy) -> WriteResult<()> {
let _ = writer;
Err(WriteError::UnsupportedNodeType)
}
/// Write the custom node as an inline element using the restricted inline writer proxy.
///
/// Inline custom nodes should implement this method to emit valid inline content.
fn write_inline(&self, writer: &mut InlineWriterProxy) -> WriteResult<()> {
let _ = writer;
Err(WriteError::UnsupportedNodeType)
}
/// Writes the HTML representation of the custom node to the provided HTML writer.
///
/// By default, this writes an HTML comment indicating that HTML rendering is not implemented
/// for this custom node type. When using the `custom_node` macro with `html_impl=true`,
/// this method delegates to the user-defined `write_html_custom` method.
///
/// Users should either:
/// 1. Override this method directly, or
/// 2. Use the `custom_node` macro with `html_impl=true` and implement the `write_html_custom` method.
fn html_write(&self, writer: &mut HtmlWriter) -> HtmlWriteResult<()> {
writer.write_trusted_html(&format!(
"<!-- HTML rendering not implemented for Custom Node: {} -->",
self.type_name()
))?;
Ok(())
}
/// Clone the custom node
fn clone_box(&self) -> Box<dyn CustomNode>;
/// Check if two custom nodes are equal
fn eq_box(&self, other: &dyn CustomNode) -> bool;
/// Whether the custom node is a block element
fn is_block(&self) -> bool;
/// Convert to Any for type casting
fn as_any(&self) -> &dyn Any;
/// Convert to mutable Any for type casting
fn as_any_mut(&mut self) -> &mut dyn Any;
/// Get the type name of the custom node for pattern matching
fn type_name(&self) -> &'static str {
std::any::type_name::<Self>()
}
}
// NOTE: CustomNodeWriter trait is deprecated and will be removed in a future version.
// Custom nodes should now directly use the provided writer proxies instead.
/*
/// Trait for custom node writer implementation
pub trait CustomNodeWriter {
/// Write a string to the output
fn write_str(&mut self, s: &str) -> std::fmt::Result;
/// Write a character to the output
fn write_char(&mut self, c: char) -> std::fmt::Result;
}
*/
// Implement Clone for Box<dyn CustomNode>
impl Clone for Box<dyn CustomNode> {
fn clone(&self) -> Self {
self.clone_box()
}
}
// Implement PartialEq for Box<dyn CustomNode>
impl PartialEq for Box<dyn CustomNode> {
fn eq(&self, other: &Self) -> bool {
self.eq_box(&**other)
}
}

View file

@ -1,78 +0,0 @@
//! HTML element definitions and utilities for CommonMark AST.
//!
//! This module contains definitions for HTML elements and attributes in the AST,
//! along with utilities for safely handling HTML content.
use super::Node;
use ecow::EcoString;
/// HTML attribute
#[derive(Debug, Clone, PartialEq)]
pub struct HtmlAttribute {
/// Attribute name
pub name: EcoString,
/// Attribute value
pub value: EcoString,
}
/// HTML element
#[derive(Debug, Clone, PartialEq)]
pub struct HtmlElement {
/// HTML tag name
pub tag: EcoString,
/// HTML attributes
pub attributes: Vec<HtmlAttribute>,
/// Child nodes
pub children: Vec<Node>,
/// Whether this is a self-closing element
pub self_closing: bool,
}
impl HtmlElement {
/// Create a new HTML element
pub fn new(tag: &str) -> Self {
Self {
tag: tag.into(),
attributes: Vec::new(),
children: Vec::new(),
self_closing: false,
}
}
/// Add an attribute to the HTML element
pub fn with_attribute(mut self, name: &str, value: &str) -> Self {
self.attributes.push(HtmlAttribute {
name: name.into(),
value: value.into(),
});
self
}
/// Add multiple attributes to the HTML element
pub fn with_attributes(mut self, attrs: Vec<(&str, &str)>) -> Self {
for (name, value) in attrs {
self.attributes.push(HtmlAttribute {
name: name.into(),
value: value.into(),
});
}
self
}
/// Add child nodes to the HTML element
pub fn with_children(mut self, children: Vec<Node>) -> Self {
self.children = children;
self
}
/// Set whether the element is self-closing
pub fn self_closing(mut self, is_self_closing: bool) -> Self {
self.self_closing = is_self_closing;
self
}
/// Check if this element's tag matches any in the provided list (case-insensitive)
pub fn tag_matches_any(&self, tags: &[EcoString]) -> bool {
tags.iter().any(|tag| tag.eq_ignore_ascii_case(&self.tag))
}
}

View file

@ -1,17 +0,0 @@
//! Abstract Syntax Tree for CommonMark document structure.
//!
//! This module defines various node types for representing CommonMark documents,
//! including headings, paragraphs, lists, code blocks, etc.
mod custom;
mod html;
mod node;
pub mod tables;
pub use self::custom::CustomNode;
pub use self::html::{HtmlAttribute, HtmlElement};
pub use self::node::{CodeBlockType, HeadingType, ListItem, Node};
// Re-export GFM specific types when the GFM feature is enabled
#[cfg(feature = "gfm")]
pub use self::node::{TableAlignment, TaskListStatus};

View file

@ -1,389 +0,0 @@
//! Node definitions for the CommonMark AST.
use super::custom::CustomNode;
use super::html::HtmlElement;
use ecow::EcoString;
use std::boxed::Box;
/// Code block type according to CommonMark specification
#[derive(Debug, Clone, PartialEq, Default)]
pub enum CodeBlockType {
/// Indented code block - composed of one or more indented chunks, each preceded by four or more spaces
Indented,
/// Fenced code block - surrounded by backtick or tilde fences
#[default]
Fenced,
}
/// Heading type according to CommonMark specification
#[derive(Debug, Clone, PartialEq, Default)]
pub enum HeadingType {
/// ATX Type - Beginning with #
#[default]
Atx,
/// Setext Type - Underlined or overlined text
Setext,
}
/// Table column alignment options for GFM tables
#[cfg(feature = "gfm")]
#[derive(Debug, Clone, PartialEq, Default)]
pub enum TableAlignment {
/// Left alignment (default)
#[default]
Left,
/// Center alignment
Center,
/// Right alignment
Right,
/// No specific alignment specified
None,
}
/// Task list item status for GFM task lists
#[cfg(feature = "gfm")]
#[derive(Debug, Clone, PartialEq)]
pub enum TaskListStatus {
/// Checked/completed task
Checked,
/// Unchecked/incomplete task
Unchecked,
}
/// Main node type, representing an element in a CommonMark document
#[derive(Debug, Clone, PartialEq)]
pub enum Node {
/// Root document node, contains child nodes
Document(Vec<Node>),
// Leaf blocks
// Thematic breaks
/// Thematic break (horizontal rule)
ThematicBreak,
// ATX headings & Setext headings
/// Heading, contains level (1-6) and inline content
Heading {
/// Heading level, 1-6
level: u8,
/// Heading content, containing inline elements
content: Vec<Node>,
/// Heading type (ATX or Setext)
heading_type: HeadingType,
},
// Indented code blocks & Fenced code blocks
/// Code block, containing optional language identifier and content
CodeBlock {
/// Optional language identifier (None for indented code blocks, Some for fenced code blocks)
language: Option<EcoString>,
/// Code content
content: EcoString,
/// The type of code block (Indented or Fenced)
block_type: CodeBlockType,
},
// HTML blocks
/// HTML block
HtmlBlock(EcoString),
// Link reference definitions
/// Link reference definition
LinkReferenceDefinition {
/// Link label (used for reference)
label: EcoString,
/// Link destination URL
destination: EcoString,
/// Optional link title
title: Option<EcoString>,
},
// Paragraphs
/// Paragraph node, containing inline elements
Paragraph(Vec<Node>),
// Blank lines - typically handled during parsing, not represented in AST
// Container blocks
// Block quotes
/// Block quote, containing any block-level elements
BlockQuote(Vec<Node>),
// & List items and Lists
/// Ordered list, containing starting number and list items
OrderedList {
/// List starting number
start: u32,
/// List items
items: Vec<ListItem>,
},
/// Unordered list, containing list items
UnorderedList(Vec<ListItem>),
/// Table (extension to CommonMark)
Table {
/// Header cells
headers: Vec<Node>,
/// Column alignments for the table
#[cfg(feature = "gfm")]
alignments: Vec<TableAlignment>,
/// Table rows, each row containing multiple cells
rows: Vec<Vec<Node>>,
},
// Inlines
// Code spans
/// Inline code
InlineCode(EcoString),
// Emphasis and strong emphasis
/// Emphasis (italic)
Emphasis(Vec<Node>),
/// Strong emphasis (bold)
Strong(Vec<Node>),
/// Strikethrough (GFM extension)
Strikethrough(Vec<Node>),
// Links
/// Link
Link {
/// Link URL
url: EcoString,
/// Optional link title
title: Option<EcoString>,
/// Link text
content: Vec<Node>,
},
/// Reference link
ReferenceLink {
/// Link reference label
label: EcoString,
/// Link text content (optional, if empty it's a shortcut reference)
content: Vec<Node>,
},
// Images
/// Image
Image {
/// Image URL
url: EcoString,
/// Optional image title
title: Option<EcoString>,
/// Alternative text, containing inline elements
alt: Vec<Node>,
},
// Autolinks
/// Autolink (URI or email wrapped in < and >)
Autolink {
/// Link URL
url: EcoString,
/// Whether this is an email autolink
is_email: bool,
},
/// GFM Extended Autolink (without angle brackets, automatically detected)
ExtendedAutolink(EcoString),
// Raw HTML
/// HTML inline element
HtmlElement(HtmlElement),
// Hard line breaks
/// Hard break (two spaces followed by a line break, or backslash followed by a line break)
HardBreak,
// Soft line breaks
/// Soft break (single line break)
SoftBreak,
// Textual content
/// Plain text
Text(EcoString),
/// Custom node that allows users to implement their own writing behavior
Custom(Box<dyn CustomNode>),
}
impl Default for Node {
fn default() -> Self {
Node::Document(vec![])
}
}
/// List item type
#[derive(Debug, Clone, PartialEq)]
pub enum ListItem {
/// Unordered list item
Unordered {
/// List item content, containing one or more block-level elements
content: Vec<Node>,
},
/// Ordered list item
Ordered {
/// Optional item number for ordered lists, allowing manual numbering
number: Option<u32>,
/// List item content, containing one or more block-level elements
content: Vec<Node>,
},
/// Task list item (GFM extension)
#[cfg(feature = "gfm")]
Task {
/// Task completion status
status: TaskListStatus,
/// List item content, containing one or more block-level elements
content: Vec<Node>,
},
}
impl Node {
/// Check if a node is a block-level node
pub fn is_block(&self) -> bool {
match self {
Node::Custom(node) => node.is_block(),
_ => matches!(
self,
Node::Document(_)
// Leaf blocks
| Node::ThematicBreak
| Node::Heading { .. }
| Node::CodeBlock { .. }
| Node::HtmlBlock(_)
| Node::LinkReferenceDefinition { .. }
| Node::Paragraph(_)
// Container blocks
| Node::BlockQuote(_)
| Node::OrderedList { .. }
| Node::UnorderedList(_)
| Node::Table { .. }
),
}
}
/// Check if a node is an inline node
pub fn is_inline(&self) -> bool {
match self {
Node::Custom(node) => !node.is_block(),
_ => matches!(
self,
// Inlines
// Code spans
Node::InlineCode(_)
// Emphasis and strong emphasis
| Node::Emphasis(_)
| Node::Strong(_)
| Node::Strikethrough(_)
// Links
| Node::Link { .. }
| Node::ReferenceLink { .. }
// Images
| Node::Image { .. }
// Autolinks
| Node::Autolink { .. }
| Node::ExtendedAutolink(_)
// Raw HTML
| Node::HtmlElement(_)
// Hard line breaks
| Node::HardBreak
// Soft line breaks
| Node::SoftBreak
// Textual content
| Node::Text(_)
),
}
}
/// Create a heading node
///
/// # Arguments
/// * `level` - Heading level (1-6)
/// * `content` - Heading content
///
/// # Returns
/// A new heading node, default ATX type
pub fn heading(level: u8, content: Vec<Node>) -> Self {
Node::Heading {
level,
content,
heading_type: HeadingType::default(),
}
}
/// Create a code block node
///
/// # Arguments
/// * `language` - Optional language identifier
/// * `content` - Code content
///
/// # Returns
/// A new code block node, default Fenced type
pub fn code_block(language: Option<EcoString>, content: EcoString) -> Self {
Node::CodeBlock {
language,
content,
block_type: CodeBlockType::default(),
}
}
/// Create a strikethrough node
///
/// # Arguments
/// * `content` - Content to be struck through
///
/// # Returns
/// A new strikethrough node
pub fn strikethrough(content: Vec<Node>) -> Self {
Node::Strikethrough(content)
}
/// Create a task list item
///
/// # Arguments
/// * `status` - Task completion status
/// * `content` - Task content
///
/// # Returns
/// A new task list item
#[cfg(feature = "gfm")]
pub fn task_list_item(status: TaskListStatus, content: Vec<Node>) -> ListItem {
ListItem::Task { status, content }
}
/// Create a table with alignment
///
/// # Arguments
/// * `headers` - Table header cells
/// * `alignments` - Column alignments
/// * `rows` - Table rows
///
/// # Returns
/// A new table node with alignment information
#[cfg(feature = "gfm")]
pub fn table_with_alignment(
headers: Vec<Node>,
alignments: Vec<TableAlignment>,
rows: Vec<Vec<Node>>,
) -> Self {
Node::Table {
headers,
alignments,
rows,
}
}
/// Check if a custom node is of a specific type, and return a reference to that type
pub fn as_custom_type<T: CustomNode + 'static>(&self) -> Option<&T> {
if let Node::Custom(node) = self {
node.as_any().downcast_ref::<T>()
} else {
None
}
}
/// Check if a node is a custom node of a specific type
pub fn is_custom_type<T: CustomNode + 'static>(&self) -> bool {
self.as_custom_type::<T>().is_some()
}
}

Some files were not shown because too many files have changed in this diff Show more