├── .github
└── workflows
│ └── build-pkg.yml
├── .gitignore
├── .vscode
└── extensions.json
├── LICENSE
├── README.md
├── app-icon.png
├── index.html
├── package.json
├── pnpm-lock.yaml
├── public
├── imgs
│ ├── app-icon.png
│ ├── screen_editing.png
│ ├── screen_preview.png
│ └── screen_projects.png
├── tauri.svg
└── vite.svg
├── src-tauri
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── assets
│ └── fonts
│ │ ├── DejaVuSansMono-Bold.ttf
│ │ ├── DejaVuSansMono-BoldOblique.ttf
│ │ ├── DejaVuSansMono-Oblique.ttf
│ │ ├── DejaVuSansMono.ttf
│ │ ├── LinLibertine_R.ttf
│ │ ├── LinLibertine_RB.ttf
│ │ ├── LinLibertine_RBI.ttf
│ │ ├── LinLibertine_RI.ttf
│ │ ├── NewCMMath-Book.otf
│ │ └── NewCMMath-Regular.otf
├── build.rs
├── capabilities
│ └── default.json
├── gen
│ └── schemas
│ │ ├── acl-manifests.json
│ │ ├── capabilities.json
│ │ ├── desktop-schema.json
│ │ └── macOS-schema.json
├── icons
│ ├── 128x128.png
│ ├── 128x128@2x.png
│ ├── 32x32.png
│ ├── Square107x107Logo.png
│ ├── Square142x142Logo.png
│ ├── Square150x150Logo.png
│ ├── Square284x284Logo.png
│ ├── Square30x30Logo.png
│ ├── Square310x310Logo.png
│ ├── Square44x44Logo.png
│ ├── Square71x71Logo.png
│ ├── Square89x89Logo.png
│ ├── StoreLogo.png
│ ├── icon.icns
│ ├── icon.ico
│ └── icon.png
├── src
│ ├── cmd.rs
│ ├── data.json
│ ├── ipc
│ │ ├── commands
│ │ │ ├── clipboard.rs
│ │ │ ├── fs.rs
│ │ │ ├── mod.rs
│ │ │ └── typst.rs
│ │ ├── events
│ │ │ ├── mod.rs
│ │ │ └── view.rs
│ │ ├── mod.rs
│ │ └── model.rs
│ ├── lib.rs
│ ├── main.rs
│ └── project
│ │ ├── manager.rs
│ │ ├── mod.rs
│ │ ├── package.rs
│ │ ├── project.rs
│ │ └── world.rs
└── tauri.conf.json
├── src
├── App.vue
├── assets
│ ├── rendering.svg
│ └── vue.svg
├── components
│ ├── MonacoEditor.vue
│ ├── MoveBar.vue
│ └── PageLoading.vue
├── main.ts
├── pages
│ ├── home
│ │ ├── Home.vue
│ │ ├── Sidebar.vue
│ │ └── SidebarToggle.vue
│ ├── project
│ │ ├── AddProject.vue
│ │ ├── Project.vue
│ │ └── interface.ts
│ └── typst
│ │ ├── DiagnosticsTip.vue
│ │ ├── PreviewPage.vue
│ │ ├── TypstEditor.vue
│ │ ├── ViewScale.vue
│ │ └── interface.ts
├── router.ts
├── shared
│ ├── lang
│ │ ├── bibtex.json
│ │ ├── completion.ts
│ │ ├── grammar.ts
│ │ ├── typst-config.json
│ │ └── typst-tm.json
│ ├── monaco-hook.ts
│ ├── move-hook.ts
│ └── util.ts
├── store
│ └── store.ts
├── style
│ ├── base.css
│ └── styles.css
└── vite-env.d.ts
├── tests
├── app-icon.png
├── basic-resume
│ └── main.typ
├── main.typ
└── works.bib
├── tsconfig.json
└── vite.config.mjs
/.github/workflows/build-pkg.yml:
--------------------------------------------------------------------------------
1 | name: "build pkg"
2 |
3 | on:
4 | push:
5 | branches:
6 | - release
7 |
8 | jobs:
9 | publish-tauri:
10 | permissions:
11 | contents: write
12 | strategy:
13 | fail-fast: false
14 | matrix:
15 | platform: [ windows-latest ]
16 | runs-on: ${{ matrix.platform }}
17 | steps:
18 | - uses: actions/checkout@v3
19 | - name: setup node
20 | uses: actions/setup-node@v3
21 | with:
22 | node-version: 20
23 | - name: install Rust stable
24 | uses: dtolnay/rust-toolchain@stable
25 |
26 | - name: install frontend dependencies
27 | run: |
28 | npm install -g pnpm
29 | pnpm install --frozen-lockfile
30 | - name: configure macOS build
31 | if: matrix.platform == 'macos-latest'
32 | run: |
33 | # Ensure that we have the aarch64 toolchain available for cross-compilation
34 | rustup target add aarch64-apple-darwin
35 | - uses: tauri-apps/tauri-action@v0
36 | env:
37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
38 | with:
39 | args: ${{ matrix.platform == 'macos-latest' && '--target universal-apple-darwin' || '' }}
40 | tagName: v__VERSION__.${{ github.run_number }} # the action automatically replaces \_\_VERSION\_\_ with the app version
41 | releaseName: "Development Build v__VERSION__.${{ github.run_number }}"
42 | releaseBody: "This is a development release from the master branch (Commit ${{ github.sha }})."
43 | releaseDraft: false
44 | prerelease: true
45 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 | */**/target/
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "Vue.volar",
4 | "tauri-apps.tauri-vscode",
5 | "rust-lang.rust-analyzer"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Lixu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # typster
4 |
5 | A desktop application for a new markup-based typesetting language, [typst](https://github.com/typst/typst).
6 | Typster is built using [Tauri](https://tauri.app/).
7 |
8 |
9 | # features
10 | - [x] auto code completion
11 | - [x] export PDF file
12 | - [x] error tip
13 | - [x] use latest typst v0.12
14 |
15 |
16 | ## screenshot
17 |
18 |
19 | 
20 |
21 |
22 |
23 | 
24 |
25 |
26 |
27 | 
28 |
29 |
30 | # Download
31 |
32 |
33 | [download link](https://github.com/wflixu/typster/releases)
34 |
35 | ### MacOS
36 |
37 |
38 | ```
39 | xattr -c /Applications/typster.app
40 | ```
41 | ### rebuild app icon
42 |
43 | ```
44 | pnpm tauri icon
45 | ```
46 |
47 |
48 | ## Other similar projects:
49 |
50 | - https://github.com/Cubxity/typster
51 | - https://github.com/Enter-tainer/typst-preview
52 |
53 | ## Related projects
54 | - https://github.com/Enter-tainer/typstyle
55 | - https://github.com/nvarner/typst-lsp
56 |
--------------------------------------------------------------------------------
/app-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/app-icon.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Tauri + Vue + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "typster",
3 | "private": true,
4 | "version": "0.12.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vue-tsc --noEmit && vite build",
9 | "preview": "vite preview",
10 | "tauri": "tauri",
11 | "td": "tauri dev",
12 | "tb": "tauri build",
13 | "start": "pnpm tauri dev",
14 | "pack": "pnpm tauri build"
15 | },
16 | "dependencies": {
17 | "@ant-design/icons-vue": "^7.0.1",
18 | "@tauri-apps/api": "^2.0.3",
19 | "@tauri-apps/plugin-dialog": "^2.0.1",
20 | "@tauri-apps/plugin-fs": "^2.0.1",
21 | "ant-design-vue": "~4.2.5",
22 | "monaco-editor": "^0.46.0",
23 | "pinia": "^2.2.4",
24 | "radash": "^12.1.0",
25 | "today-ui": "^0.0.23",
26 | "vscode-oniguruma": "^2.0.1",
27 | "vscode-textmate": "^9.1.0",
28 | "vue": "^3.5.12",
29 | "vue-router": "^4.4.5"
30 | },
31 | "devDependencies": {
32 | "@tauri-apps/cli": "^2.0.4",
33 | "@types/node": "^20.17.1",
34 | "@vitejs/plugin-vue": "^5.1.4",
35 | "typescript": "^5.6.3",
36 | "vite": "^5.4.10",
37 | "vue-tsc": "^2.1.8"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/public/imgs/app-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/public/imgs/app-icon.png
--------------------------------------------------------------------------------
/public/imgs/screen_editing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/public/imgs/screen_editing.png
--------------------------------------------------------------------------------
/public/imgs/screen_preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/public/imgs/screen_preview.png
--------------------------------------------------------------------------------
/public/imgs/screen_projects.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/public/imgs/screen_projects.png
--------------------------------------------------------------------------------
/public/tauri.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src-tauri/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | /target/
4 |
5 |
--------------------------------------------------------------------------------
/src-tauri/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "typster"
3 | version = "0.2.0"
4 | description = "A Tauri App"
5 | authors = ["you"]
6 | license = ""
7 | repository = ""
8 | edition = "2021"
9 | rust-version = "1.60"
10 |
11 | [lib]
12 | name = "typster_lib"
13 | crate-type = ["staticlib", "cdylib", "rlib"]
14 |
15 |
16 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
17 |
18 | [build-dependencies]
19 | tauri-build = { version = "2.0.3", features = [] }
20 |
21 | [dependencies]
22 | anyhow = "1.0"
23 | thiserror = "1.0"
24 | arboard = "3.3"
25 | base64 = "0.22"
26 | enumset = { version = "1.1", features = ["serde"] }
27 | png = "0.17"
28 | parking_lot = "0.12.1"
29 |
30 | hex = "0.4"
31 |
32 | tauri = { version = "2.1.1", features = [ "macos-private-api", "devtools", ] }
33 |
34 | tauri-plugin-clipboard = "2"
35 | tauri-plugin-fs = "2"
36 | tauri-plugin-dialog = "2"
37 |
38 | serde = { version = "1.0", features = ["derive"] }
39 | serde_json = "1.0"
40 | serde_repr = "0.1"
41 |
42 | siphasher = "1.0"
43 | tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
44 |
45 |
46 | log = "0.4"
47 | env_logger = "0.11"
48 | notify = "6.1"
49 | comemo = "0.4.0"
50 | chrono = { version = "0.4.24", default-features = false, features = ["clock", "std", "serde"] }
51 | dirs = "5.0"
52 | walkdir = "2.5"
53 | memmap2 = "0.9"
54 | once_cell = "1.19"
55 |
56 |
57 | typst = { version = "0.12.0" }
58 | typst-ide = { version = "0.12.0" }
59 | typst-pdf = { version = "0.12.0" }
60 | typst-render = { version = "0.12.0" }
61 | typst-syntax = { version = "0.12.0" }
62 | typst-kit = { version = "0.12.0" }
63 | typst-timing = { version = "0.12.0" }
64 | typst-utils = { version = "0.12.0" }
65 |
66 | ureq = { version = "2", default-features = false, features = ["native-tls", "gzip", "json"] }
67 | ecow = { version = "0.2", features = ["serde"] }
68 | native-tls = "0.2"
69 | env_proxy = "0.4"
70 | flate2 = "1"
71 | tar = "0.4"
72 |
--------------------------------------------------------------------------------
/src-tauri/assets/fonts/DejaVuSansMono-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/src-tauri/assets/fonts/DejaVuSansMono-Bold.ttf
--------------------------------------------------------------------------------
/src-tauri/assets/fonts/DejaVuSansMono-BoldOblique.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/src-tauri/assets/fonts/DejaVuSansMono-BoldOblique.ttf
--------------------------------------------------------------------------------
/src-tauri/assets/fonts/DejaVuSansMono-Oblique.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/src-tauri/assets/fonts/DejaVuSansMono-Oblique.ttf
--------------------------------------------------------------------------------
/src-tauri/assets/fonts/DejaVuSansMono.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/src-tauri/assets/fonts/DejaVuSansMono.ttf
--------------------------------------------------------------------------------
/src-tauri/assets/fonts/LinLibertine_R.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/src-tauri/assets/fonts/LinLibertine_R.ttf
--------------------------------------------------------------------------------
/src-tauri/assets/fonts/LinLibertine_RB.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/src-tauri/assets/fonts/LinLibertine_RB.ttf
--------------------------------------------------------------------------------
/src-tauri/assets/fonts/LinLibertine_RBI.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/src-tauri/assets/fonts/LinLibertine_RBI.ttf
--------------------------------------------------------------------------------
/src-tauri/assets/fonts/LinLibertine_RI.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/src-tauri/assets/fonts/LinLibertine_RI.ttf
--------------------------------------------------------------------------------
/src-tauri/assets/fonts/NewCMMath-Book.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/src-tauri/assets/fonts/NewCMMath-Book.otf
--------------------------------------------------------------------------------
/src-tauri/assets/fonts/NewCMMath-Regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/src-tauri/assets/fonts/NewCMMath-Regular.otf
--------------------------------------------------------------------------------
/src-tauri/build.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | tauri_build::build()
3 | }
4 |
--------------------------------------------------------------------------------
/src-tauri/capabilities/default.json:
--------------------------------------------------------------------------------
1 | {
2 | "identifier": "main-capability",
3 | "description": "Capability for the main window",
4 | "windows": [
5 | "main"
6 | ],
7 | "permissions": [
8 | "core:default",
9 | "fs:allow-read-file",
10 | "fs:allow-write-file",
11 | "fs:allow-write-text-file",
12 | "fs:allow-read-dir",
13 | "fs:allow-copy-file",
14 | "fs:allow-mkdir",
15 | "fs:allow-remove",
16 | "fs:allow-rename",
17 | "fs:allow-exists",
18 | "dialog:allow-save",
19 | {
20 | "identifier": "fs:scope",
21 | "allow": [
22 | "$HOME/**/*"
23 | ]
24 | },
25 | {
26 | "identifier": "fs:allow-read-dir",
27 | "allow": [
28 | "$HOME/**/*"
29 | ]
30 | },
31 | {
32 | "identifier": "fs:allow-read-file",
33 | "allow": [
34 | "$HOME/**/*"
35 | ]
36 | },
37 | {
38 | "identifier": "fs:allow-write-file",
39 | "allow": [
40 | "$HOME/**/*"
41 | ]
42 | },
43 | "core:window:allow-center",
44 | "core:window:allow-set-position",
45 | "core:window:allow-set-size",
46 | "core:window:allow-set-maximizable",
47 | "core:window:allow-set-minimizable",
48 | "fs:default"
49 | ]
50 | }
--------------------------------------------------------------------------------
/src-tauri/gen/schemas/capabilities.json:
--------------------------------------------------------------------------------
1 | {"main-capability":{"identifier":"main-capability","description":"Capability for the main window","local":true,"windows":["main"],"permissions":["core:default","fs:allow-read-file","fs:allow-write-file","fs:allow-write-text-file","fs:allow-read-dir","fs:allow-copy-file","fs:allow-mkdir","fs:allow-remove","fs:allow-rename","fs:allow-exists","dialog:allow-save",{"identifier":"fs:scope","allow":["$HOME/**/*"]},{"identifier":"fs:allow-read-dir","allow":["$HOME/**/*"]},{"identifier":"fs:allow-read-file","allow":["$HOME/**/*"]},{"identifier":"fs:allow-write-file","allow":["$HOME/**/*"]},"core:window:allow-center","core:window:allow-set-position","core:window:allow-set-size","core:window:allow-set-maximizable","core:window:allow-set-minimizable","fs:default"]}}
--------------------------------------------------------------------------------
/src-tauri/icons/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/src-tauri/icons/128x128.png
--------------------------------------------------------------------------------
/src-tauri/icons/128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/src-tauri/icons/128x128@2x.png
--------------------------------------------------------------------------------
/src-tauri/icons/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/src-tauri/icons/32x32.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square107x107Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/src-tauri/icons/Square107x107Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square142x142Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/src-tauri/icons/Square142x142Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square150x150Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/src-tauri/icons/Square150x150Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square284x284Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/src-tauri/icons/Square284x284Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square30x30Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/src-tauri/icons/Square30x30Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square310x310Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/src-tauri/icons/Square310x310Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square44x44Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/src-tauri/icons/Square44x44Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square71x71Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/src-tauri/icons/Square71x71Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/Square89x89Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/src-tauri/icons/Square89x89Logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/StoreLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/src-tauri/icons/StoreLogo.png
--------------------------------------------------------------------------------
/src-tauri/icons/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/src-tauri/icons/icon.icns
--------------------------------------------------------------------------------
/src-tauri/icons/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/src-tauri/icons/icon.ico
--------------------------------------------------------------------------------
/src-tauri/icons/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wflixu/typster/34afb9b69a9db6cc89c4e7e682a3122160acb0ab/src-tauri/icons/icon.png
--------------------------------------------------------------------------------
/src-tauri/src/cmd.rs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
5 | #[tauri::command]
6 | pub fn greet(name: &str) -> String {
7 | format!("Hello, {}! You've been greeted from Rust!", name)
8 | }
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src-tauri/src/data.json:
--------------------------------------------------------------------------------
1 | {
2 | "children": [
3 | {
4 | "children": [
5 | {
6 | "position": {
7 | "end": {
8 | "column": 23,
9 | "line": 1,
10 | "offset": 22
11 | },
12 | "start": {
13 | "column": 4,
14 | "line": 1,
15 | "offset": 3
16 | }
17 | },
18 | "type": "text",
19 | "value": "MDX 能干什么?"
20 | }
21 | ],
22 | "depth": 2,
23 | "position": {
24 | "end": {
25 | "column": 23,
26 | "line": 1,
27 | "offset": 22
28 | },
29 | "start": {
30 | "column": 1,
31 | "line": 1,
32 | "offset": 0
33 | }
34 | },
35 | "type": "heading"
36 | },
37 | {
38 | "children": [
39 | {
40 | "position": {
41 | "end": {
42 | "column": 42,
43 | "line": 3,
44 | "offset": 65
45 | },
46 | "start": {
47 | "column": 1,
48 | "line": 3,
49 | "offset": 24
50 | }
51 | },
52 | "type": "text",
53 | "value": "它能够让我们在 markdown 中写 JSX"
54 | }
55 | ],
56 | "position": {
57 | "end": {
58 | "column": 42,
59 | "line": 3,
60 | "offset": 65
61 | },
62 | "start": {
63 | "column": 1,
64 | "line": 3,
65 | "offset": 24
66 | }
67 | },
68 | "type": "paragraph"
69 | },
70 | {
71 | "children": [
72 | {
73 | "position": {
74 | "end": {
75 | "column": 40,
76 | "line": 5,
77 | "offset": 106
78 | },
79 | "start": {
80 | "column": 1,
81 | "line": 5,
82 | "offset": 67
83 | }
84 | },
85 | "type": "text",
86 | "value": "这是 2018 年,降水量柱状图。"
87 | }
88 | ],
89 | "position": {
90 | "end": {
91 | "column": 40,
92 | "line": 5,
93 | "offset": 106
94 | },
95 | "start": {
96 | "column": 1,
97 | "line": 5,
98 | "offset": 67
99 | }
100 | },
101 | "type": "paragraph"
102 | },
103 | {
104 | "children": [
105 | {
106 | "alt": "",
107 | "position": {
108 | "end": {
109 | "column": 20,
110 | "line": 7,
111 | "offset": 127
112 | },
113 | "start": {
114 | "column": 1,
115 | "line": 7,
116 | "offset": 108
117 | }
118 | },
119 | "title": null,
120 | "type": "image",
121 | "url": "./app-icon.png"
122 | }
123 | ],
124 | "position": {
125 | "end": {
126 | "column": 20,
127 | "line": 7,
128 | "offset": 127
129 | },
130 | "start": {
131 | "column": 1,
132 | "line": 7,
133 | "offset": 108
134 | }
135 | },
136 | "type": "paragraph"
137 | }
138 | ],
139 | "position": {
140 | "end": {
141 | "column": 1,
142 | "line": 8,
143 | "offset": 128
144 | },
145 | "start": {
146 | "column": 1,
147 | "line": 1,
148 | "offset": 0
149 | }
150 | },
151 | "type": "root"
152 | }
--------------------------------------------------------------------------------
/src-tauri/src/ipc/commands/clipboard.rs:
--------------------------------------------------------------------------------
1 | use super::{Error, Result};
2 | use crate::ipc::commands::project_path;
3 | use crate::project::ProjectManager;
4 | use arboard::Clipboard;
5 | use chrono::Local;
6 | use log::info;
7 | use serde::Serialize;
8 | use std::fs;
9 | use std::fs::File;
10 | use std::io::BufWriter;
11 | use std::path::PathBuf;
12 | use std::sync::Arc;
13 | use tauri::Runtime;
14 |
15 | #[derive(Serialize, Debug)]
16 | pub struct ClipboardPasteResponse {
17 | path: PathBuf,
18 | }
19 |
20 | #[tauri::command]
21 | pub async fn clipboard_paste(
22 | window: tauri::Window,
23 | project_manager: tauri::State<'_, Arc>>,
24 | ) -> Result {
25 | let now = Local::now();
26 | let (_, path) = project_path(&window, &project_manager, PathBuf::from("assets"))?;
27 |
28 | let now_format = now.format("%Y-%m-%d %H:%M:%S.png");
29 |
30 | fs::create_dir_all(&path).map_err(Into::::into)?;
31 | let path = path.join(now_format.to_string());
32 |
33 | // TODO: Better error handling
34 | let mut clipboard = Clipboard::new().map_err(|_| Error::Unknown)?;
35 | let data = clipboard.get_image().map_err(|_| Error::Unknown)?;
36 |
37 | let file = File::create(&path).map_err(Into::::into)?;
38 | let ref mut w = BufWriter::new(file);
39 | let mut encoder = png::Encoder::new(w, data.width as u32, data.height as u32);
40 | encoder.set_color(png::ColorType::Rgba);
41 | encoder.set_depth(png::BitDepth::Eight);
42 |
43 | let mut writer = encoder.write_header().map_err(|_| Error::Unknown)?;
44 | writer
45 | .write_image_data(&*data.bytes)
46 | .map_err(|_| Error::Unknown)?;
47 |
48 | info!(
49 | "wrote {}x{} image from clipboard to {:?}",
50 | data.width, data.height, path
51 | );
52 | Ok(ClipboardPasteResponse {
53 | path: PathBuf::from(format!("assets/{}", now_format)),
54 | })
55 | }
56 |
--------------------------------------------------------------------------------
/src-tauri/src/ipc/commands/fs.rs:
--------------------------------------------------------------------------------
1 | use super::{Error, Result};
2 | use crate::ipc::commands::project_path;
3 | use crate::project::{Project, ProjectManager};
4 | use ecow::eco_format;
5 | use enumset::EnumSetType;
6 | use log::info;
7 | use serde::Serialize;
8 | use std::cmp::Ordering;
9 | use std::fs;
10 | use std::fs::{File, OpenOptions};
11 | use std::io::Write;
12 | use std::path::PathBuf;
13 | use std::sync::Arc;
14 | use tauri::{Runtime, State, Window};
15 | use typst::foundations::Smart;
16 | use typst_pdf::{PdfOptions, PdfStandard, PdfStandards};
17 | use chrono::{DateTime, Datelike, Utc, Timelike};
18 | use typst::foundations::Datetime;
19 |
20 | /// Convert [`chrono::DateTime`] to [`Datetime`]
21 | pub fn convert_datetime(date_time:DateTime) -> Option {
22 | Datetime::from_ymd_hms(
23 | date_time.year(),
24 | date_time.month().try_into().ok()?,
25 | date_time.day().try_into().ok()?,
26 | date_time.hour().try_into().ok()?,
27 | date_time.minute().try_into().ok()?,
28 | date_time.second().try_into().ok()?,
29 | )
30 | }
31 |
32 | #[derive(EnumSetType, Serialize, Debug)]
33 | #[serde(rename_all = "snake_case")]
34 | pub enum FileType {
35 | File,
36 | Directory,
37 | }
38 |
39 | #[derive(Serialize, Debug)]
40 | pub struct FileItem {
41 | pub name: String,
42 | #[serde(rename = "type")]
43 | pub file_type: FileType,
44 | }
45 |
46 | #[tauri::command]
47 | pub async fn load_project_from_path(
48 | window: Window,
49 | project_manager: State<'_, Arc>>,
50 | path: String,
51 | ) -> std::result::Result<(), Error> {
52 | let path_buf = PathBuf::from(&path);
53 | let project = Arc::new(Project::load_from_path(path_buf));
54 | project_manager.set_project(&window, Some(project));
55 | info!("succeed load_project_from_path {}", &path);
56 |
57 | Ok(())
58 | }
59 |
60 | #[tauri::command]
61 | pub async fn export_pdf(
62 | window: Window,
63 | project_manager: State<'_, Arc>>,
64 | path: String,
65 | ) -> std::result::Result {
66 | info!("Starting export of PDF to path: {}", &path);
67 |
68 | let path_buf = PathBuf::from(&path);
69 |
70 | // 获取项目
71 | if let Some(project) = project_manager.get_project(&window) {
72 | let cache = project.cache.read().expect("Failed to read cache");
73 |
74 | if let Some(doc) = &cache.document {
75 | // 创建 CompileCommand
76 |
77 | let options = PdfOptions {
78 | ident: Smart::Auto,
79 | timestamp: convert_datetime(Utc::now()),
80 | page_ranges: None,
81 | standards: PdfStandards::new(&[PdfStandard::A_2b, PdfStandard::V_1_7]).unwrap(),
82 | };
83 |
84 | // 生成 PDF
85 | let buffer = typst_pdf::pdf(doc, &options).map_err(|e| Error::Unknown)?;
86 |
87 | // 写入 PDF 文件
88 | fs::write(&path_buf, &buffer)?;
89 |
90 | info!("PDF successfully exported to {}", path_buf.display());
91 | return Ok(buffer.len() as u64);
92 | } else {
93 | return Err(Error::UnknownProject);
94 | }
95 | } else {
96 | return Err(Error::UnknownProject);
97 | }
98 | }
99 |
100 | /// Reads raw bytes from a specified path.
101 | /// Note that this command is slow compared to the text API due to Wry's
102 | /// messaging system in v1. See: https://github.com/tauri-apps/tauri/issues/1817
103 | #[tauri::command]
104 | pub async fn fs_read_file_binary(
105 | window: Window,
106 | project_manager: State<'_, Arc>>,
107 | path: PathBuf,
108 | ) -> std::result::Result, Error> {
109 | let (_, path) = project_path(&window, &project_manager, path)?;
110 | fs::read(path).map_err(Into::into)
111 | }
112 |
113 | #[tauri::command]
114 | pub async fn fs_read_file_text(
115 | window: Window,
116 | project_manager: State<'_, Arc>>,
117 | path: PathBuf,
118 | ) -> std::result::Result {
119 | if path.is_absolute() {
120 | return fs::read_to_string(path).map_err(Into::into);
121 | }
122 | let (_, path) = project_path(&window, &project_manager, path)?;
123 | fs::read_to_string(path).map_err(Into::into)
124 | }
125 |
126 | #[tauri::command]
127 | pub async fn fs_create_file(
128 | window: Window,
129 | project_manager: State<'_, Arc>>,
130 | path: PathBuf,
131 | ) -> std::result::Result<(), Error> {
132 | let (_, path) = project_path(&window, &project_manager, path)?;
133 |
134 | // Not sure if there's a scenario where this condition is not met
135 | // unless the project is located at `/`
136 | if let Some(parent) = path.parent() {
137 | fs::create_dir_all(parent).map_err(Into::::into)?;
138 | }
139 | OpenOptions::new()
140 | .read(true)
141 | .write(true)
142 | .create_new(true)
143 | .open(&*path)
144 | .map_err(Into::::into)?;
145 |
146 | Ok(())
147 | }
148 |
149 | #[tauri::command]
150 | pub async fn fs_write_file_binary(
151 | window: Window,
152 | project_manager: State<'_, Arc>>,
153 | path: PathBuf,
154 | content: Vec,
155 | ) -> std::result::Result<(), Error> {
156 | let (_, path) = project_path(&window, &project_manager, path)?;
157 | fs::write(path, content).map_err(Into::into)
158 | }
159 |
160 | #[tauri::command]
161 | pub async fn fs_write_file_text(
162 | window: Window,
163 | project_manager: State<'_, Arc>>,
164 | path: PathBuf,
165 | content: String,
166 | ) -> std::result::Result<(), Error> {
167 | let (project, absolute_path) = project_path(&window, &project_manager, &path)?;
168 | let _ = File::create(absolute_path)
169 | .map(|mut f| f.write_all(content.as_bytes()))
170 | .map_err(Into::::into)?;
171 |
172 | let mut world = project.world.lock().unwrap();
173 | let _ = world
174 | .slot_update(&path, Some(content))
175 | .map_err(Into::::into)?;
176 | Ok(())
177 | }
178 |
179 | #[tauri::command]
180 | pub async fn fs_list_dir(
181 | window: Window,
182 | project_manager: State<'_, Arc>>,
183 | path: PathBuf,
184 | ) -> std::result::Result, Error> {
185 | let (_, path) = project_path(&window, &project_manager, path)?;
186 | let list = fs::read_dir(path).map_err(Into::::into)?;
187 |
188 | let mut files: Vec = vec![];
189 | list.into_iter().for_each(|entry| {
190 | if let Ok(entry) = entry {
191 | if let (Ok(file_type), Ok(name)) = (entry.file_type(), entry.file_name().into_string())
192 | {
193 | // File should only be directory or file.
194 | // Symlinks should be resolved in project_path.
195 | let t = if file_type.is_dir() {
196 | FileType::Directory
197 | } else {
198 | FileType::File
199 | };
200 | files.push(FileItem { name, file_type: t });
201 | }
202 | }
203 | });
204 |
205 | files.sort_by(|a, b| {
206 | if a.file_type == FileType::Directory && b.file_type == FileType::File {
207 | Ordering::Less
208 | } else if a.file_type == FileType::File && b.file_type == FileType::Directory {
209 | Ordering::Greater
210 | } else {
211 | a.name.cmp(&b.name)
212 | }
213 | });
214 |
215 | Ok(files)
216 | }
217 |
--------------------------------------------------------------------------------
/src-tauri/src/ipc/commands/mod.rs:
--------------------------------------------------------------------------------
1 | mod clipboard;
2 | mod fs;
3 | mod typst;
4 |
5 | pub use self::typst::*;
6 | pub use clipboard::*;
7 | pub use fs::*;
8 |
9 | use crate::project::{Project, ProjectManager};
10 | use ::typst::diag::FileError;
11 | use serde::{Serialize, Serializer};
12 | use std::io;
13 | use std::path::{Component, Path, PathBuf};
14 | use std::sync::Arc;
15 | use tauri::{Runtime, State, Window};
16 | use anyhow::{self, Context};
17 |
18 |
19 | #[derive(thiserror::Error, Debug)]
20 | pub enum Error {
21 | #[error("unknown error")]
22 | Unknown,
23 | #[error("unknown project")]
24 | UnknownProject,
25 | #[error("io error occurred")]
26 | IO(#[from] io::Error),
27 | #[error("typst file error occurred")]
28 | TypstFile(#[from] FileError),
29 | #[error("the provided path does not belong to the project")]
30 | UnrelatedPath,
31 | }
32 |
33 | impl Serialize for Error {
34 | fn serialize(&self, serializer: S) -> std::result::Result
35 | where
36 | S: Serializer,
37 | {
38 | serializer.serialize_str(self.to_string().as_ref())
39 | }
40 | }
41 |
42 | pub type Result = std::result::Result;
43 |
44 | /// Retrieves the project and resolves the path. Furthermore,
45 | /// this function will resolve the path relative to project's root
46 | /// and checks whether the path belongs to the project root.
47 | pub fn project(
48 | window: &Window,
49 | project_manager: &State>>,
50 | ) -> Result> {
51 | project_manager
52 | .get_project(window)
53 | .ok_or(Error::UnknownProject)
54 | }
55 |
56 | /// Retrieves the project and resolves the path. Furthermore,
57 | /// this function will resolve the path relative to project's root
58 | /// and checks whether the path belongs to the project root.
59 | pub fn project_path>(
60 | window: &Window,
61 | project_manager: &State>>,
62 | path: P,
63 | ) -> Result<(Arc, PathBuf)> {
64 | let project = project_manager
65 | .get_project(window)
66 | .ok_or(Error::UnknownProject)?;
67 | let root_len = project.root.as_os_str().len();
68 | let mut out = project.root.to_path_buf();
69 | for component in path.as_ref().components() {
70 | match component {
71 | Component::Prefix(_) => {}
72 | Component::RootDir => {}
73 | Component::CurDir => {}
74 | Component::ParentDir => {
75 | out.pop();
76 | if out.as_os_str().len() < root_len {
77 | return Err(Error::UnrelatedPath);
78 | }
79 | }
80 | Component::Normal(_) => out.push(component),
81 | }
82 | }
83 | Ok((project, out))
84 | }
85 |
--------------------------------------------------------------------------------
/src-tauri/src/ipc/commands/typst.rs:
--------------------------------------------------------------------------------
1 | use super::{Error, Result};
2 | use crate::ipc::commands::project;
3 | use crate::ipc::model::TypstRenderResponse;
4 | use crate::ipc::{
5 | TypstCompileEvent, TypstDiagnosticSeverity, TypstDocument, TypstPage, TypstSourceDiagnostic,
6 | };
7 | use crate::project::ProjectManager;
8 | use base64::Engine;
9 | use log::{debug, info};
10 | use serde::Serialize;
11 | use serde_repr::Serialize_repr;
12 | use siphasher::sip128::{Hasher128, SipHasher};
13 | use std::hash::Hash;
14 | use std::ops::Range;
15 | use std::path::PathBuf;
16 | use std::sync::Arc;
17 | use std::time::Instant;
18 | use tauri::Runtime;
19 | use typst::diag::Severity;
20 | use typst::visualize::Color;
21 | use typst::World;
22 | use typst_ide::{Completion, CompletionKind};
23 |
24 | #[derive(Serialize_repr, Debug)]
25 | #[repr(u8)]
26 | pub enum TypstCompletionKind {
27 | Syntax = 1,
28 | Function = 2,
29 | Parameter = 3,
30 | Constant = 4,
31 | Symbol = 5,
32 | Type = 6,
33 | }
34 |
35 | #[derive(Serialize, Debug)]
36 | pub struct TypstCompletion {
37 | kind: TypstCompletionKind,
38 | label: String,
39 | apply: Option,
40 | detail: Option,
41 | }
42 |
43 | #[derive(Serialize, Debug)]
44 | pub struct TypstCompleteResponse {
45 | offset: usize,
46 | completions: Vec,
47 | }
48 |
49 | impl From for TypstCompletion {
50 | fn from(value: Completion) -> Self {
51 | Self {
52 | kind: match value.kind {
53 | CompletionKind::Syntax => TypstCompletionKind::Syntax,
54 | CompletionKind::Func => TypstCompletionKind::Function,
55 | CompletionKind::Param => TypstCompletionKind::Parameter,
56 | CompletionKind::Constant => TypstCompletionKind::Constant,
57 | CompletionKind::Symbol(_) => TypstCompletionKind::Symbol,
58 | CompletionKind::Type => TypstCompletionKind::Type,
59 | },
60 | label: value.label.to_string(),
61 | apply: value.apply.map(|s| s.to_string()),
62 | detail: value.detail.map(|s| s.to_string()),
63 | }
64 | }
65 | }
66 | // todo delete
67 | #[tauri::command]
68 | pub async fn typst_slot_update(
69 | window: tauri::Window,
70 | project_manager: tauri::State<'_, Arc>>,
71 | path: PathBuf,
72 | content: String,
73 | ) -> Result<()> {
74 | let project = project(&window, &project_manager)?;
75 |
76 | let mut world = project.world.lock().unwrap();
77 | let _ = world
78 | .slot_update(&path, Some(content))
79 | .map_err(Into::::into)?;
80 | Ok(())
81 | }
82 |
83 | #[tauri::command]
84 | pub async fn typst_compile_doc(
85 | window: tauri::Window,
86 | project_manager: tauri::State<'_, Arc>>,
87 | path: PathBuf,
88 | content: String,
89 | ) -> Result<(Vec, Vec)> {
90 |
91 | let project = project(&window, &project_manager)?;
92 | let mut world = project.world.lock().unwrap();
93 | let source_id = world
94 | .slot_update(&path, Some(content.clone()))
95 | .map_err(Into::::into)?;
96 |
97 | if !world.is_main_set() {
98 |
99 | let config = project.config.read().unwrap();
100 | if config.apply_main(&project, &mut world).is_err() {
101 | debug!("skipped compilation for {:?} (main not set)", project);
102 | return Err(Error::Unknown);
103 | }
104 | }
105 |
106 | let now = Instant::now();
107 |
108 | let mut pages: Vec = Vec::new();
109 | let mut diags: Vec = Vec::new();
110 | match typst::compile(&*world).output {
111 | Ok(doc) => {
112 | let elapsed = now.elapsed();
113 | debug!(
114 | "compilation succeeded for {:?} in {:?} ms",
115 | project,
116 | elapsed.as_millis()
117 | );
118 | let mut idx: u32 = 0;
119 | for page in &doc.pages {
120 | let mut hasher = SipHasher::new();
121 | page.frame.hash(&mut hasher);
122 | let hash = hex::encode(hasher.finish128().as_bytes());
123 | let width = page.frame.width().to_pt();
124 | let height = page.frame.height().to_pt();
125 | idx += 1;
126 | let pag = TypstPage {
127 | num: idx,
128 | width,
129 | height,
130 | hash: hash.clone(),
131 | };
132 | pages.push(pag);
133 | }
134 |
135 | project.cache.write().unwrap().document = Some(doc);
136 | }
137 | Err(diagnostics) => {
138 | debug!("compilation failed with {:?} diagnostics", &diagnostics);
139 |
140 | let source = world.source(source_id);
141 | let diagnostics: Vec = match source {
142 | Ok(source) => diagnostics
143 | .iter()
144 | .filter(|d| d.span.id() == Some(source_id))
145 | .filter_map(|d| {
146 | let span = source.find(d.span)?;
147 | let range = span.range();
148 |
149 | let message = d.message.to_string();
150 | Some(TypstSourceDiagnostic {
151 | pos: get_range_position(&content, range.clone()),
152 | range,
153 | severity: match d.severity {
154 | Severity::Error => TypstDiagnosticSeverity::Error,
155 | Severity::Warning => TypstDiagnosticSeverity::Warning,
156 | },
157 | message,
158 | hints: d.hints.iter().map(|hint| hint.to_string()).collect(),
159 | })
160 | })
161 | .collect(),
162 | Err(_) => vec![],
163 | };
164 |
165 | diags = diagnostics.clone();
166 | }
167 | }
168 |
169 | Ok((pages, diags))
170 | }
171 |
172 |
173 | pub fn get_range_position(text: &str, rang: Range) -> (usize, usize) {
174 | let mut ln = 0;
175 | let mut cn = 0;
176 | let mut total: usize = 0;
177 | for line in text.lines() {
178 |
179 | ln += 1;
180 | let row = line.chars().count() + 1;
181 |
182 | if total <= rang.start && rang.start <= total + row {
183 | cn = rang.start - total;
184 | break;
185 | }
186 |
187 | total += row;
188 | }
189 | return (ln, cn);
190 | }
191 |
192 | #[tauri::command]
193 | pub async fn typst_render(
194 | window: tauri::Window,
195 | project_manager: tauri::State<'_, Arc>>,
196 | page: usize,
197 | scale: f32,
198 | nonce: u32,
199 | ) -> Result {
200 | info!(
201 | "typst_render page:{} scale: {} nonce: {}",
202 | page, scale, nonce
203 | );
204 | let project = project_manager
205 | .get_project(&window)
206 | .ok_or(Error::UnknownProject)?;
207 |
208 | let cache = project.cache.read().unwrap();
209 |
210 | if let Some(p) = cache
211 | .document
212 | .as_ref()
213 | .and_then(|doc| doc.pages.get(page - 1))
214 | {
215 | let now = Instant::now();
216 |
217 | let bmp = typst_render::render(p, scale);
218 | if let Ok(image) = bmp.encode_png() {
219 | let elapsed = now.elapsed();
220 | debug!(
221 | "rendering complete for page {} in {} ms",
222 | page,
223 | elapsed.as_millis()
224 | );
225 | let b64 = base64::engine::general_purpose::STANDARD.encode(image);
226 | return Ok(TypstRenderResponse {
227 | image: b64,
228 | width: bmp.width(),
229 | height: bmp.height(),
230 | nonce,
231 | });
232 | }
233 | }
234 |
235 | Err(Error::Unknown)
236 | }
237 |
238 | #[tauri::command]
239 | pub async fn typst_autocomplete(
240 | window: tauri::Window,
241 | project_manager: tauri::State<'_, Arc>>,
242 | path: PathBuf,
243 | content: String,
244 | offset: usize,
245 | explicit: bool,
246 | ) -> Result {
247 | let project = project(&window, &project_manager)?;
248 | let mut world = project.world.lock().unwrap();
249 |
250 | let offset = content
251 | .char_indices()
252 | .nth(offset)
253 | .map(|a| a.0)
254 | .unwrap_or(content.len());
255 |
256 | // TODO: Improve error typing
257 | let source_id = world
258 | .slot_update(&*path, Some(content.clone()))
259 | .map_err(Into::::into)?;
260 |
261 | let source = world.source(source_id).map_err(Into::::into)?;
262 |
263 | let (completed_offset, completions) =
264 | typst_ide::autocomplete(&*world, None, &source, offset, explicit)
265 | .ok_or_else(|| Error::Unknown)?;
266 |
267 | let completed_char_offset = content[..completed_offset].chars().count();
268 | Ok(TypstCompleteResponse {
269 | offset: completed_char_offset,
270 | completions: completions.into_iter().map(TypstCompletion::from).collect(),
271 | })
272 | }
273 |
--------------------------------------------------------------------------------
/src-tauri/src/ipc/events/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod view;
2 |
--------------------------------------------------------------------------------
/src-tauri/src/ipc/events/view.rs:
--------------------------------------------------------------------------------
1 | use serde::Serialize;
2 | use tauri::{Emitter, Runtime, Window};
3 |
4 | // For some reason, Tauri requires an event payload...
5 | #[derive(Debug, Clone, Serialize)]
6 | struct EmptyPayload {}
7 |
8 | // Instructs the front-end to hide or show the preview
9 | pub fn toggle_preview_visibility(window: &Window) {
10 | let _ = window.emit("toggle_preview_visibility", EmptyPayload {});
11 | }
12 |
--------------------------------------------------------------------------------
/src-tauri/src/ipc/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod commands;
2 | pub mod events;
3 |
4 | mod model;
5 | pub use model::*;
6 |
--------------------------------------------------------------------------------
/src-tauri/src/ipc/model.rs:
--------------------------------------------------------------------------------
1 | use serde::Serialize;
2 | use std::ops::Range;
3 | use std::path::PathBuf;
4 |
5 | #[derive(Serialize, Clone, Debug)]
6 | pub struct TypstCompileEvent {
7 | pub document: Option,
8 | pub diagnostics: Option>,
9 | }
10 |
11 | #[derive(Serialize, Clone, Debug)]
12 | pub struct TypstDocument {
13 | pub pages: usize,
14 | pub hash: String,
15 | pub width: f64,
16 | pub height: f64,
17 | }
18 |
19 |
20 | #[derive(Serialize, Clone, Debug)]
21 | pub struct TypstPage {
22 | pub num: u32,
23 | pub hash: String,
24 | pub width: f64,
25 | pub height: f64,
26 |
27 | }
28 |
29 | #[derive(Serialize, Clone, Debug)]
30 | #[serde(rename_all = "snake_case")]
31 | pub enum TypstDiagnosticSeverity {
32 | Error,
33 | Warning,
34 | }
35 |
36 | #[derive(Serialize, Clone, Debug)]
37 | pub struct TypstSourceDiagnostic {
38 | pub range: Range,
39 | pub severity: TypstDiagnosticSeverity,
40 | pub message: String,
41 | pub hints: Vec,
42 | pub pos:(usize, usize)
43 | }
44 |
45 | #[derive(Serialize, Clone, Debug)]
46 | pub struct TypstRenderResponse {
47 | pub image: String,
48 | pub width: u32,
49 | pub height: u32,
50 | pub nonce: u32,
51 | }
52 |
53 | #[derive(Serialize, Clone, Debug)]
54 | pub struct ProjectChangeEvent {
55 | pub project: Option,
56 | }
57 |
58 | #[derive(Serialize, Clone, Debug)]
59 | pub struct ProjectModel {
60 | pub root: PathBuf,
61 | }
62 |
63 | #[derive(Serialize, Clone, Debug)]
64 | pub struct FSRefreshEvent {
65 | pub path: PathBuf,
66 | }
67 |
--------------------------------------------------------------------------------
/src-tauri/src/lib.rs:
--------------------------------------------------------------------------------
1 | // Prevents additional console window on Windows in release, DO NOT REMOVE!!
2 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
3 | #![allow(unused_imports, unused_variables, dead_code, unused_mut)]
4 |
5 | mod cmd;
6 | mod ipc;
7 | mod project;
8 |
9 | use crate::project::ProjectManager;
10 | use env_logger::Env;
11 | use log::info;
12 | use std::sync::Arc;
13 | use tauri::Wry;
14 |
15 | pub fn run() {
16 | env_logger::init_from_env(Env::default().default_filter_or("debug"));
17 | info!("initializing typster");
18 |
19 | let project_manager = Arc::new(ProjectManager::::new());
20 | if let Ok(watcher) = ProjectManager::init_watcher(project_manager.clone()) {
21 | project_manager.set_watcher(watcher);
22 | }
23 |
24 | tauri::Builder::default()
25 | .plugin(tauri_plugin_fs::init())
26 | .plugin(tauri_plugin_dialog::init())
27 | .manage(project_manager)
28 | .invoke_handler(tauri::generate_handler![
29 | cmd::greet,
30 | ipc::commands::fs_list_dir,
31 | ipc::commands::fs_read_file_binary,
32 | ipc::commands::fs_read_file_text,
33 | ipc::commands::fs_create_file,
34 | ipc::commands::fs_write_file_binary,
35 | ipc::commands::fs_write_file_text,
36 | ipc::commands::load_project_from_path,
37 | ipc::commands::typst_compile_doc,
38 | ipc::commands::typst_render,
39 | ipc::commands::typst_autocomplete,
40 | ipc::commands::typst_slot_update,
41 | ipc::commands::export_pdf,
42 | ipc::commands::clipboard_paste
43 | ])
44 | .run(tauri::generate_context!())
45 | .expect("error while running tauri application");
46 | }
47 |
--------------------------------------------------------------------------------
/src-tauri/src/main.rs:
--------------------------------------------------------------------------------
1 | // Prevents additional console window on Windows in release, DO NOT REMOVE!!
2 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
3 |
4 |
5 | #[tokio::main]
6 | async fn main() {
7 | typster_lib::run()
8 | }
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src-tauri/src/project/manager.rs:
--------------------------------------------------------------------------------
1 | use crate::ipc::{FSRefreshEvent, ProjectChangeEvent, ProjectModel};
2 | use crate::project::{is_project_config_file, Project, ProjectConfig};
3 | use log::{debug, error, info, trace, warn};
4 | use notify::event::ModifyKind;
5 | use notify::{Config, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
6 | use std::collections::HashMap;
7 | use std::path::{Path, PathBuf};
8 | use std::sync::{Arc, Mutex, RwLock};
9 | use tauri::{Emitter, Runtime, WebviewWindow, Window};
10 | use tokio::sync::mpsc::channel;
11 |
12 | #[derive(Clone, Copy, Debug)]
13 | enum FSHandleKind {
14 | Refresh,
15 | Reload,
16 | }
17 |
18 | pub struct ProjectManager {
19 | projects: RwLock, Arc>>,
20 | watcher: Mutex