commit ccd51eb19ab7e0e44117008c84a555319e5a0601 Author: Myriad-Dreamin Date: Thu Mar 7 05:29:31 2024 +0800 dev: init diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..d40f8149 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/target/ +/editors/lapce/target/ +result* +.direnv +.envrc +node_modules/ +/editors/vscode/out/ +/editors/lapce/out/ diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..a8642997 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,10 @@ +.git/** +.github/** +.vscode/** +assets/** +src/** +target/** +*.toml +*.txt +*.lock +*.md diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..61ed5d47 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "rust-lang.rust-analyzer" + ] +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..e555bf26 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,31 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}/editors/vscode" + ], + "outFiles": [ + "${workspaceFolder}/editors/vscode/out/**/*.js" + ], + "preLaunchTask": "VS Code Extension Prelaunch" + }, + { + "name": "Run Extension [Jaeger]", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}/editors/vscode" + ], + "outFiles": [ + "${workspaceFolder}/editors/vscode/out/**/*.js" + ], + "preLaunchTask": "VS Code Extension Prelaunch [Jaeger]" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..997a3f50 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "eslint.format.enable": true, + "eslint.workingDirectories": [ + "editors/vscode" + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..fcd56a7e --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,75 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "VS Code Extension Prelaunch", + "dependsOn": [ + "Compile VS Code Extension", + "Build Debug LSP Binary", + "Copy Debug LSP Binary to VS Code Extension" + ], + "dependsOrder": "sequence", + }, + { + "label": "VS Code Extension Prelaunch [Jaeger]", + "dependsOn": [ + "Compile VS Code Extension", + "Build Debug LSP Binary [Jaeger]", + "Copy Debug LSP Binary to VS Code Extension" + ], + "dependsOrder": "sequence" + }, + { + "label": "Compile VS Code Extension", + "type": "npm", + "script": "compile", + "path": "editors/vscode", + "group": "build", + }, + { + "label": "Build Debug LSP Binary", + "type": "cargo", + "command": "build", + "args": [ "--bin", "tinymist" ], + "problemMatcher": [ + "$rustc" + ], + "group": "build" + }, + { + "label": "Build Debug LSP Binary [Jaeger]", + "type": "cargo", + "command": "build", + "args": [ "--bin", "tinymist", "--features", "jaeger" ], + "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/" + ] + } + } + ] +} diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..714d1fcc --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4636 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" + +[[package]] +name = "append-only-vec" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb8f874ecf419dd8165d0279746de966cb8966636d028845e3bd65d519812a" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "async-trait" +version = "0.1.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "auto_impl" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "await-tree" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626aa057fb6d254883c2750ef6bcbe6f6a5ce45daff839b538708411794f794d" +dependencies = [ + "coarsetime", + "derive_builder", + "flexstr", + "indextree", + "itertools 0.11.0", + "parking_lot", + "pin-project", + "tokio", + "tracing", + "weak-table", +] + +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64-serde" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba368df5de76a5bea49aaf0cf1b39ccfbbef176924d1ba5db3e4135216cbe3c7" +dependencies = [ + "base64", + "serde", +] + +[[package]] +name = "biblatex" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a3638fc10f65e552d53318e042cefa542418633451163228fcbfb1a58a0ca85" +dependencies = [ + "numerals", + "paste", + "strum", + "unicode-normalization", + "unscanny", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +dependencies = [ + "serde", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" + +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bytemuck" +version = "1.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "camino" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "694c8807f2ae16faecc43dc17d74b3eb042482789fd0eb64b39a2e04e087053f" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cc" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0ba8f7aaa012f30d5b2861462f6708eccd49c3c39863fe083a308035f63d723" +dependencies = [ + "jobserver", + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chinese-number" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49fccaef6346f6d6a741908d3b79fe97c2debe2fbb5eb3a7d00ff5981b52bb6c" +dependencies = [ + "chinese-variant", + "enum-ordinalize", + "num-bigint", + "num-traits", +] + +[[package]] +name = "chinese-variant" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7588475145507237ded760e52bf2f1085495245502033756d28ea72ade0e498b" + +[[package]] +name = "chrono" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets 0.52.4", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "citationberg" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c15a0bf8014b266d11f20451dc9202d8d26180ffd8b094d73ecbe74d821f01fb" +dependencies = [ + "quick-xml 0.28.2", + "serde", +] + +[[package]] +name = "clap" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim 0.11.0", +] + +[[package]] +name = "clap_complete" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "885e4d7d5af40bfb99ae6f9433e292feac98d452dcb3ec3d25dfe7552b77da8c" +dependencies = [ + "clap", +] + +[[package]] +name = "clap_derive" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "clap_mangen" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1dd95b5ebb5c1c54581dd6346f3ed6a79a3eef95dd372fc2ac13d535535300e" +dependencies = [ + "clap", + "roff", +] + +[[package]] +name = "coarsetime" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b3839cf01bb7960114be3ccf2340f541b6d0c81f8690b007b2b39f750f7e5d" +dependencies = [ + "libc", + "wasix", + "wasm-bindgen", +] + +[[package]] +name = "cobs" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "comemo" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf5705468fa80602ee6a5f9318306e6c428bffd53e43209a78bc05e6e667c6f4" +dependencies = [ + "comemo-macros", + "siphasher 1.0.0", +] + +[[package]] +name = "comemo-macros" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54af6ac68ada2d161fa9cc1ab52676228e340866d094d6542107e74b82acc095" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "core_maths" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b02505ccb8c50b0aa21ace0fc08c3e53adebd4e58caa18a36152803c7709a3" +dependencies = [ + "libm", +] + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core 0.14.4", + "darling_macro 0.14.4", +] + +[[package]] +name = "darling" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +dependencies = [ + "darling_core 0.20.8", + "darling_macro 0.20.8", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 1.0.109", +] + +[[package]] +name = "darling_core" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 2.0.52", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core 0.14.4", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +dependencies = [ + "darling_core 0.20.8", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.3", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" + +[[package]] +name = "data-url" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "derive_builder" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" +dependencies = [ + "darling 0.14.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder_macro" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" +dependencies = [ + "derive_builder_core", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "dissimilar" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86e3bdc80eee6e16b2b6b0f87fbc98c04bee3455e35174c0de1a125d0688c632" + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "ecow" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ea5e3f9cda726431da9d1a8d5a29785d544b31e98e1ca7a210906244002e02" +dependencies = [ + "serde", +] + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "elsa" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98e71ae4df57d214182a2e5cb90230c0192c6ddfcaa05c36453d46a54713e10" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enum-ordinalize" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fancy-regex" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" +dependencies = [ + "bit-set", + "regex", +] + +[[package]] +name = "fast-srgb8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "fdeflate" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "filetime" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys 0.52.0", +] + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flexstr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d50aef14619d336a54fca5a592d952eb39037b1a1e7e6afd9f91c892ac7ef65" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "fontconfig-parser" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a595cb550439a117696039dfc69830492058211b771a2a165379f2a1a53d84d" +dependencies = [ + "roxmltree 0.19.0", +] + +[[package]] +name = "fontdb" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020e203f177c0fb250fb19455a252e838d2bbbce1f80f25ecc42402aafa8cd38" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2 0.8.0", + "slotmap", + "tinyvec", + "ttf-parser", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "fst" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab85b9b05e3978cc9a9cf8fea7f01b494e1a09ed3037e16ba39edc7a29eb61a" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gif" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "git2" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b3ba52851e73b46a4c3df1d89343741112003f0f6f13beb0dfac9e457c3fdcd" +dependencies = [ + "bitflags 2.4.2", + "libc", + "libgit2-sys", + "log", + "url", +] + +[[package]] +name = "h2" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.2.5", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "hayagriva" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f97c07366b7f686741521ca63cc14baf18cea53c39b0c09873cd1d4a1b2b8c" +dependencies = [ + "biblatex", + "ciborium", + "citationberg", + "indexmap 2.2.5", + "numerals", + "paste", + "serde", + "serde_yaml", + "thiserror", + "unic-langid", + "unicode-segmentation", + "unscanny", + "url", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "hypher" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94bf16dd62ea2bec617a6f8a3e1ba03107311783069a647787ac689d1f35321e" + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "137d96353afc8544d437e8a99eceb10ab291352699573b0de5b08bda38c78c60" +dependencies = [ + "displaydoc", + "serde", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c0aa2536adc14c07e2a521e95512b75ed8ef832f0fdf9299d4a0a45d2be2a9d" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c17d8f6524fdca4471101dd71f0a132eb6382b5d6d7f2970441cb25f6f435a" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "545c6c3e8bf9580e2dafee8de6f9ec14826aaf359787789c7724f1f85f47d3dc" + +[[package]] +name = "icu_properties" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976e296217453af983efa25f287a4c1da04b9a63bf1ed63719455068e4453eb5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "serde", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6a86c0e384532b06b6c104814f9c1b13bcd5b64409001c0d05713a1f3529d99" + +[[package]] +name = "icu_provider" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba58e782287eb6950247abbf11719f83f5d4e4a5c1f2cd490d30a334bc47c2f4" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "postcard", + "serde", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_adapters" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a229f978260da7c3aabb68cb7dc7316589936680570fe55e50fdd3f97711a4dd" +dependencies = [ + "icu_locid", + "icu_locid_transform", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_provider_blob" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a7202cddda672db167c6352719959e9b01cb1ca576d32fa79103f61b5a73601" +dependencies = [ + "icu_provider", + "postcard", + "serde", + "writeable", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2abdd3a62551e8337af119c5899e600ca0c88ec8f23a46c60ba216c803dcf1a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "icu_segmenter" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2dc1e8f4ba33a6a4956770ac5c08570f255d6605519fb3a859a0c0a270a2f8f" +dependencies = [ + "core_maths", + "displaydoc", + "icu_collections", + "icu_locid", + "icu_provider", + "icu_segmenter_data", + "serde", + "utf8_iter", + "zerovec", +] + +[[package]] +name = "icu_segmenter_data" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3673d6698dcffce08cfe8fc5da3c11c3f2c663d5d6137fd58ab2cbf44235ab46" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "if_chain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" + +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "gif", + "jpeg-decoder", + "num-traits", + "png", +] + +[[package]] +name = "imagesize" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", + "serde", +] + +[[package]] +name = "indexmap-nostd" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" + +[[package]] +name = "indextree" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c40411d0e5c63ef1323c3d09ce5ec6d84d71531e18daed0743fccea279d7deb6" + +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "jobserver" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" +dependencies = [ + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + +[[package]] +name = "kurbo" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd85a5776cd9500c2e2059c8c76c3b01528566b7fcbaf8098b55a33fc298849b" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libgit2-sys" +version = "0.16.2+1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8" +dependencies = [ + "cc", + "libc", + "libz-sys", + "pkg-config", +] + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.2", + "libc", + "redox_syscall", +] + +[[package]] +name = "libz-sys" +version = "1.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037731f5d3aaa87a5675e895b63ddff1a87624bc29f77004ea829809654e48f6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "line-wrap" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" +dependencies = [ + "safemem", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "lipsum" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c5e9ef2d2ad6fe67a59ace27c203c8d3a71d195532ee82e3bbe0d5f9a9ca541" +dependencies = [ + "rand", + "rand_chacha", +] + +[[package]] +name = "litemap" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d642685b028806386b2b6e75685faadd3eb65a85fff7df711ce18446a422da" +dependencies = [ + "serde", +] + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "lsp-types" +version = "0.94.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66bfd44a06ae10647fe3f8214762e9369fd4248df1350924b4ef9e770a85ea1" +dependencies = [ + "bitflags 1.3.2", + "serde", + "serde_json", + "serde_repr", + "url", +] + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "memmap2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed" +dependencies = [ + "libc", +] + +[[package]] +name = "memmap2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +dependencies = [ + "libc", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "notify" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +dependencies = [ + "bitflags 2.4.2", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "walkdir", + "windows-sys 0.48.0", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "numerals" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e25be21376a772d15f97ae789845340a9651d3c4246ff5ebb6a2b35f9c37bd31" + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "open" +version = "5.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b3fbb0d52bf0cbb5225ba3d2c303aa136031d43abff98284332a9981ecddec" +dependencies = [ + "is-wsl", + "libc", + "pathdiff", +] + +[[package]] +name = "openssl" +version = "0.10.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +dependencies = [ + "bitflags 2.4.2", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "palette" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebfc23a4b76642983d57e4ad00bb4504eb30a8ce3c70f4aee1f725610e36d97a" +dependencies = [ + "approx", + "fast-srgb8", + "libm", + "palette_derive", +] + +[[package]] +name = "palette_derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8890702dbec0bad9116041ae586f84805b13eecd1d8b1df27c29998a9969d6d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "path-clean" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef" + +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + +[[package]] +name = "pdf-writer" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644b654f2de28457bf1e25a4905a76a563d1128a33ce60cf042f721f6818feaf" +dependencies = [ + "bitflags 1.3.2", + "itoa", + "memchr", + "ryu", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "plist" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5699cc8a63d1aa2b1ee8e12b9ad70ac790d65788cd36101fa37f87ea46c4cef" +dependencies = [ + "base64", + "indexmap 2.2.5", + "line-wrap", + "quick-xml 0.31.0", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "postcard" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a55c51ee6c0db07e68448e336cf8ea4131a620edefebf9893e759b2d793420f8" +dependencies = [ + "cobs", + "embedded-io", + "serde", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "psm" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +dependencies = [ + "cc", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quick-xml" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quick-xml" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rayon" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rctree" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f" + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "reqwest" +version = "0.11.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body", + "hyper", + "hyper-rustls", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rkyv" +version = "0.7.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cba464629b3394fc4dbc6f940ff8f5b4ff5c7aef40f29166fd4ad12acbc99c0" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7dddfff8de25e6f62b9d64e6e432bf1c6736c57d20323e15ee10435fbda7c65" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "roff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" + +[[package]] +name = "roxmltree" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862340e351ce1b271a378ec53f304a5558f7db87f3769dc655a8f6ecbb68b302" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "roxmltree" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.21.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "rustybuzz" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71cd15fef9112a1f94ac64b58d1e4628192631ad6af4dc69997f995459c874e7" +dependencies = [ + "bitflags 1.3.2", + "bytemuck", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "serde_json" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "serde_spanned" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15d167997bd841ec232f5b2b8e0e26606df2e7caa4c31b95ea9ca52b200bd270" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.2.5", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "865f9743393e638991566a8b7a479043c2c8da94a33e0a31f18214c9cae0a64d" +dependencies = [ + "darling 0.20.8", + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "serde_yaml" +version = "0.9.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f" +dependencies = [ + "indexmap 2.2.5", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + +[[package]] +name = "simplecss" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" +dependencies = [ + "log", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "siphasher" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54ac45299ccbd390721be55b412d41931911f654fa99e2cb8bfb57184b2061fe" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "socket2" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "stacker" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "winapi", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.52", +] + +[[package]] +name = "subsetter" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09eab8a83bff89ba2200bd4c59be45c7c787f988431b936099a5a266c957f2f9" + +[[package]] +name = "svg2pdf" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a81da66842e426278f20062cd249779565e13f9ab4bfe0ac9e94eb476bc3a0f3" +dependencies = [ + "image", + "miniz_oxide", + "once_cell", + "pdf-writer", + "usvg", +] + +[[package]] +name = "svgtypes" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71499ff2d42f59d26edb21369a308ede691421f79ebc0f001e2b1fd3a7c9e52" +dependencies = [ + "kurbo", + "siphasher 0.3.11", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "syntect" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1" +dependencies = [ + "bincode", + "bitflags 1.3.2", + "fancy-regex", + "flate2", + "fnv", + "once_cell", + "plist", + "regex-syntax", + "serde", + "serde_derive", + "serde_json", + "thiserror", + "walkdir", + "yaml-rust", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tar" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "time" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +dependencies = [ + "deranged", + "itoa", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "png", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tinymist-cli" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "comemo", + "env_logger", + "futures", + "itertools 0.12.1", + "lazy_static", + "log", + "once_cell", + "parking_lot", + "percent-encoding", + "regex", + "serde", + "serde_json", + "strum", + "thiserror", + "tokio", + "tower-lsp", + "typst", + "typst-assets", + "typst-ide", + "typst-pdf", + "typst-preview", + "typst-ts-compiler", + "typst-ts-core", +] + +[[package]] +name = "tinystr" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83c02bf3c538ab32ba913408224323915f4ef9a6d61c0e85d493f355921c0ece" +dependencies = [ + "displaydoc", + "serde", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6" +dependencies = [ + "indexmap 2.2.5", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-lsp" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ba052b54a6627628d9b3c34c176e7eda8359b7da9acd497b9f20998d118508" +dependencies = [ + "async-trait", + "auto_impl", + "bytes", + "dashmap", + "futures", + "httparse", + "lsp-types", + "memchr", + "serde", + "serde_json", + "tokio", + "tokio-util", + "tower", + "tower-lsp-macros", + "tracing", +] + +[[package]] +name = "tower-lsp-macros" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "ttf-parser" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49d64318d8311fc2668e48b63969f4343e0a85c4a109aa8460d6672e364b8bd1" + +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.1.0", + "httparse", + "log", + "rand", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "typst" +version = "0.10.0" +source = "git+https://github.com/Myriad-Dreamin/typst.git?branch=typst.ts-v0.10.0-half#cce5dc159ff7275573765143767b705ec2eb8616" +dependencies = [ + "az", + "bitflags 2.4.2", + "chinese-number", + "ciborium", + "comemo", + "csv", + "ecow", + "fontdb", + "hayagriva", + "hypher", + "icu_properties", + "icu_provider", + "icu_provider_adapters", + "icu_provider_blob", + "icu_segmenter", + "image", + "indexmap 2.2.5", + "kurbo", + "lipsum", + "log", + "once_cell", + "palette", + "rayon", + "regex", + "roxmltree 0.18.1", + "rustybuzz", + "serde", + "serde_json", + "serde_yaml", + "siphasher 1.0.0", + "smallvec", + "stacker", + "syntect", + "time", + "toml", + "tracing", + "ttf-parser", + "typed-arena", + "typst-macros", + "typst-syntax", + "unicode-bidi", + "unicode-math-class", + "unicode-script", + "unicode-segmentation", + "usvg", + "wasmi", +] + +[[package]] +name = "typst-assets" +version = "0.10.0" +source = "git+https://github.com/typst/typst-assets?rev=79e1c84#79e1c84788fb23f34755b8ebb300dcd9bc4af77f" + +[[package]] +name = "typst-ide" +version = "0.10.0" +source = "git+https://github.com/Myriad-Dreamin/typst.git?branch=typst.ts-v0.10.0-half#cce5dc159ff7275573765143767b705ec2eb8616" +dependencies = [ + "comemo", + "ecow", + "if_chain", + "log", + "serde", + "typst", + "unscanny", +] + +[[package]] +name = "typst-macros" +version = "0.10.0" +source = "git+https://github.com/Myriad-Dreamin/typst.git?branch=typst.ts-v0.10.0-half#cce5dc159ff7275573765143767b705ec2eb8616" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "typst-pdf" +version = "0.10.0" +source = "git+https://github.com/Myriad-Dreamin/typst.git?branch=typst.ts-v0.10.0-half#cce5dc159ff7275573765143767b705ec2eb8616" +dependencies = [ + "base64", + "bytemuck", + "comemo", + "ecow", + "image", + "miniz_oxide", + "once_cell", + "pdf-writer", + "subsetter", + "svg2pdf", + "tracing", + "ttf-parser", + "typst", + "unicode-properties", + "unscanny", + "xmp-writer", +] + +[[package]] +name = "typst-preview" +version = "0.10.8" +dependencies = [ + "anyhow", + "await-tree", + "clap", + "clap_complete", + "clap_mangen", + "comemo", + "elsa", + "env_logger", + "futures", + "hyper", + "indexmap 2.2.5", + "log", + "memmap2 0.9.4", + "notify", + "once_cell", + "open", + "serde", + "serde_json", + "tiny-skia", + "tokio", + "tokio-tungstenite", + "typst", + "typst-ts-compiler", + "typst-ts-core", + "typst-ts-svg-exporter", + "vergen", +] + +[[package]] +name = "typst-syntax" +version = "0.10.0" +source = "git+https://github.com/Myriad-Dreamin/typst.git?branch=typst.ts-v0.10.0-half#cce5dc159ff7275573765143767b705ec2eb8616" +dependencies = [ + "comemo", + "ecow", + "once_cell", + "serde", + "tracing", + "unicode-ident", + "unicode-math-class", + "unicode-script", + "unicode-segmentation", + "unscanny", +] + +[[package]] +name = "typst-ts-compiler" +version = "0.4.2-rc6" +source = "git+https://github.com/Myriad-Dreamin/typst.ts?rev=98e3d3a42877b195f87223060882d55fd5aaa04a#98e3d3a42877b195f87223060882d55fd5aaa04a" +dependencies = [ + "append-only-vec", + "base64", + "chrono", + "codespan-reporting", + "comemo", + "dirs", + "dissimilar", + "flate2", + "fontdb", + "fst", + "hex", + "indexmap 2.2.5", + "instant", + "log", + "nohash-hasher", + "notify", + "once_cell", + "parking_lot", + "pathdiff", + "reqwest", + "rustc-hash", + "serde", + "serde_json", + "sha2", + "strum", + "tar", + "tokio", + "typst", + "typst-ts-core", + "typst-ts-svg-exporter", + "walkdir", +] + +[[package]] +name = "typst-ts-core" +version = "0.4.2-rc6" +source = "git+https://github.com/Myriad-Dreamin/typst.ts?rev=98e3d3a42877b195f87223060882d55fd5aaa04a#98e3d3a42877b195f87223060882d55fd5aaa04a" +dependencies = [ + "base64", + "base64-serde", + "bitvec", + "byteorder", + "comemo", + "crossbeam-queue", + "dashmap", + "ecow", + "elsa", + "flate2", + "fxhash", + "hex", + "log", + "once_cell", + "parking_lot", + "path-clean", + "rayon", + "rkyv", + "rustc-hash", + "serde", + "serde_json", + "serde_repr", + "serde_with", + "sha2", + "siphasher 1.0.0", + "tiny-skia", + "tiny-skia-path", + "ttf-parser", + "typst", + "xmlparser", +] + +[[package]] +name = "typst-ts-svg-exporter" +version = "0.4.2-rc6" +source = "git+https://github.com/Myriad-Dreamin/typst.ts?rev=98e3d3a42877b195f87223060882d55fd5aaa04a#98e3d3a42877b195f87223060882d55fd5aaa04a" +dependencies = [ + "base64", + "comemo", + "log", + "once_cell", + "rayon", + "siphasher 1.0.0", + "tiny-skia", + "typst", + "typst-ts-core", +] + +[[package]] +name = "unic-langid" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238722e6d794ed130f91f4ea33e01fcff4f188d92337a21297892521c72df516" +dependencies = [ + "unic-langid-impl", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd55a2063fdea4ef1f8633243a7b0524cbeef1905ae04c31a1c9b9775c55bc6" +dependencies = [ + "serde", + "tinystr", +] + +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694" + +[[package]] +name = "unicode-ccc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-math-class" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d246cf599d5fae3c8d56e04b20eb519adb89a8af8d0b0fbcded369aa3647d65" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" + +[[package]] +name = "unicode-script" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd" + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" + +[[package]] +name = "unscanny" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9df2af067a7953e9c3831320f35c1cc0600c30d44d9f7a12b01db1cd88d6b47" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "usvg" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51daa774fe9ee5efcf7b4fec13019b8119cda764d9a8b5b06df02bb1445c656" +dependencies = [ + "base64", + "log", + "pico-args", + "usvg-parser", + "usvg-text-layout", + "usvg-tree", + "xmlwriter", +] + +[[package]] +name = "usvg-parser" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45c88a5ffaa338f0e978ecf3d4e00d8f9f493e29bed0752e1a808a1db16afc40" +dependencies = [ + "data-url", + "flate2", + "imagesize", + "kurbo", + "log", + "roxmltree 0.18.1", + "simplecss", + "siphasher 0.3.11", + "svgtypes", + "usvg-tree", +] + +[[package]] +name = "usvg-text-layout" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d2374378cb7a3fb8f33894e0fdb8625e1bbc4f25312db8d91f862130b541593" +dependencies = [ + "fontdb", + "kurbo", + "log", + "rustybuzz", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "usvg-tree", +] + +[[package]] +name = "usvg-tree" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cacb0c5edeaf3e80e5afcf5b0d4004cc1d36318befc9a7c6606507e5d0f4062" +dependencies = [ + "rctree", + "strict-num", + "svgtypes", + "tiny-skia-path", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "uuid" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "vergen" +version = "8.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e27d6bdd219887a9eadd19e1c34f32e47fa332301184935c6d9bca26f3cca525" +dependencies = [ + "anyhow", + "cargo_metadata", + "cfg-if", + "git2", + "regex", + "rustc_version", + "rustversion", + "time", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasix" +version = "0.12.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d" +dependencies = [ + "wasi", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.52", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "wasmi" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8281d1d660cdf54c76a3efa9ddd0c270cada1383a995db3ccb43d166456c7" +dependencies = [ + "smallvec", + "spin", + "wasmi_arena", + "wasmi_core", + "wasmparser-nostd", +] + +[[package]] +name = "wasmi_arena" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "104a7f73be44570cac297b3035d76b169d6599637631cf37a1703326a0727073" + +[[package]] +name = "wasmi_core" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf1a7db34bff95b85c261002720c00c3a6168256dcb93041d3fa2054d19856a" +dependencies = [ + "downcast-rs", + "libm", + "num-traits", + "paste", +] + +[[package]] +name = "wasmparser-nostd" +version = "0.100.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9157cab83003221bfd385833ab587a039f5d6fa7304854042ba358a3b09e0724" +dependencies = [ + "indexmap-nostd", +] + +[[package]] +name = "weak-table" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "323f4da9523e9a669e1eaf9c6e763892769b1d38c623913647bfdc1532fe4549" + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + +[[package]] +name = "winnow" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "writeable" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad7bb64b8ef9c0aa27b6da38b452b0ee9fd82beaf276a87dd796fb55cbae14e" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "xattr" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +dependencies = [ + "libc", + "linux-raw-sys", + "rustix", +] + +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + +[[package]] +name = "xmp-writer" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4543ba138f64a94b19e1e9c66c165bca7e03d470e1c066cb76ea279d9d0e1989" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "yoke" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65e71b2e4f287f467794c671e2b8f8a5f3716b3c829079a1c44740148eff07e4" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e6936f0cce458098a201c245a11bef556c6a0181129c7034d10d76d1ec3a2b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "655b0814c5c0b19ade497851070c640773304939a6c0fd5f5fb43da0696d05b7" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6a647510471d372f2e6c2e6b7219e44d8c574d24fdc11c610a61455782f18c3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0594125a0574fb93059c92c588ab209cc036a23d1baeb3410fa9181bea551a0" +dependencies = [ + "displaydoc", + "litemap", + "serde", + "zerovec", +] + +[[package]] +name = "zerovec" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff4439ae91fb5c72b8abc12f3f2dbf51bd27e6eadb9f8a5bc8898dddb0e27ea" +dependencies = [ + "serde", + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4e5997cbf58990550ef1f0e5124a05e47e1ebd33a84af25739be6031a62c20" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..3311ec1c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,94 @@ +[workspace.package] +description = "Fast lsp implementation for typst." +authors = ["Myriad-Dreamin "] +version = "0.1.0" +edition = "2021" +readme = "README.md" +license = "Apache-2.0" +homepage = "https://github.com/_/tinymist" +repository = "https://github.com/_/tinymist" +rust-version = "1.74" + +[workspace] +resolver = "2" +members = ["crates/*", "external/typst-preview"] + +[workspace.dependencies] + +once_cell = "1" +anyhow = "1" + +fxhash = "0.2.1" +ecow = "0.2.0" +comemo = "0.3" +ena = "0.14.2" +futures = "0.3" +regex = "1.8.1" +itertools = "0.12.0" +lazy_static = "1.4.0" +env_logger = "0.11.1" +log = "0.4.17" +percent-encoding = "2.3.0" +strum = { version = "0.25.0", features = ["derive"] } +async-trait = "0.1.73" +parking_lot = "0.12.1" +thiserror = "1.0.44" + +typst = "0.10.0" +typst-ide = "0.10.0" +typst-pdf = "0.10.0" +typst-assets = { git = "https://github.com/typst/typst-assets", rev = "79e1c84" } +typst-ts-core = { git = "https://github.com/Myriad-Dreamin/typst.ts", rev = "98e3d3a42877b195f87223060882d55fd5aaa04a", package = "typst-ts-core" } +typst-ts-compiler = { version = "0.4.2-rc6" } +typst-preview = { path = "external/typst-preview" } + +tower-lsp = "0.20.0" + +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.34.0", features = [ + "macros", + "rt-multi-thread", + "io-std", +] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +divan = "0.1.7" +insta = "1.34" + +[profile.release] +lto = true # Enable link-time optimization +strip = true # Strip symbols from binary* +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" + +[workspace.lints.clippy] +uninlined_format_args = "warn" +missing_errors_doc = "warn" +missing_panics_doc = "warn" +missing_safety_doc = "warn" +undocumented_unsafe_blocks = "warn" + +[patch.crates-io] +typst = { git = "https://github.com/Myriad-Dreamin/typst.git", branch = "typst.ts-v0.10.0-half" } +typst-ide = { git = "https://github.com/Myriad-Dreamin/typst.git", branch = "typst.ts-v0.10.0-half" } +typst-pdf = { git = "https://github.com/Myriad-Dreamin/typst.git", branch = "typst.ts-v0.10.0-half" } +typst-syntax = { git = "https://github.com/Myriad-Dreamin/typst.git", branch = "typst.ts-v0.10.0-half" } +typst-ts-svg-exporter = { git = "https://github.com/Myriad-Dreamin/typst.ts", rev = "98e3d3a42877b195f87223060882d55fd5aaa04a", package = "typst-ts-svg-exporter" } +typst-ts-core = { git = "https://github.com/Myriad-Dreamin/typst.ts", rev = "98e3d3a42877b195f87223060882d55fd5aaa04a", package = "typst-ts-core" } +typst-ts-compiler = { git = "https://github.com/Myriad-Dreamin/typst.ts", rev = "98e3d3a42877b195f87223060882d55fd5aaa04a", package = "typst-ts-compiler" } diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..31931256 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + 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 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. diff --git a/README.md b/README.md new file mode 100644 index 00000000..4cbbdd8d --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# Tinymist LSP + +Tinymist [ˈtaɪni mɪst], a language server for [Typst](https://typst.app/) [taɪpst]. You can also call it "微霭" in Chinese. + +## Acknowledgements + +- It is developed based on [typst-lsp](https://github.com/nvarner/typst-lsp) diff --git a/crates/tinymist/Cargo.toml b/crates/tinymist/Cargo.toml new file mode 100644 index 00000000..8470e8c4 --- /dev/null +++ b/crates/tinymist/Cargo.toml @@ -0,0 +1,56 @@ +[package] +name = "tinymist-cli" +description = "CLI for tinymist." +categories = ["compilers", "command-line-utilities"] +keywords = ["cli", "language", "typst"] +authors.workspace = true +version.workspace = true +license.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +[[bin]] +name = "tinymist" +path = "src/main.rs" +test = false +doctest = false +bench = false +doc = false + +[dependencies] + +once_cell.workspace = true +anyhow.workspace = true +comemo.workspace = true +thiserror.workspace = true +tokio.workspace = true +futures.workspace = true +regex.workspace = true +itertools.workspace = true +lazy_static.workspace = true +strum.workspace = true +async-trait.workspace = true +env_logger.workspace = true +log.workspace = true +percent-encoding.workspace = true +serde.workspace = true +serde_json.workspace = true +parking_lot.workspace = true + +typst.workspace = true +typst-ide.workspace = true +typst-pdf.workspace = true +typst-assets = { workspace = true, features = ["fonts"] } + +typst-ts-core = { version = "0.4.2-rc6", default-features = false, features = [ + "flat-vector", + "vector-bbox", +] } +typst-ts-compiler.workspace = true +typst-preview.workspace = true + +tower-lsp.workspace = true + +# [lints] +# workspace = true diff --git a/crates/tinymist/src/actor/mod.rs b/crates/tinymist/src/actor/mod.rs new file mode 100644 index 00000000..bb461262 --- /dev/null +++ b/crates/tinymist/src/actor/mod.rs @@ -0,0 +1,2 @@ +pub mod render; +pub mod typst; diff --git a/crates/tinymist/src/actor/render.rs b/crates/tinymist/src/actor/render.rs new file mode 100644 index 00000000..2b1f1ed4 --- /dev/null +++ b/crates/tinymist/src/actor/render.rs @@ -0,0 +1,100 @@ +use std::{ + path::{Path, PathBuf}, + sync::Arc, +}; + +use anyhow::Context; +use log::info; +use tokio::sync::{ + broadcast::{self, error::RecvError}, + watch, +}; +use typst_ts_core::TypstDocument; + +use crate::config::ExportPdfMode; + +#[derive(Debug, Clone)] +pub enum RenderActorRequest { + Render, + ChangeConfig(PdfExportConfig), +} + +#[derive(Debug, Clone)] +pub struct PdfExportConfig { + path: PathBuf, + mode: ExportPdfMode, +} + +pub struct PdfExportActor { + render_rx: broadcast::Receiver, + document: watch::Receiver>>, + + config: Option, +} + +impl PdfExportActor { + pub fn new( + document: watch::Receiver>>, + render_rx: broadcast::Receiver, + ) -> Self { + Self { + render_rx, + document, + + config: None, + } + } + + pub async fn run(mut self) { + loop { + tokio::select! { + req = self.render_rx.recv() => { + let req = match req { + Ok(req) => req, + Err(RecvError::Closed) => { + info!("render actor channel closed"); + break; + } + Err(RecvError::Lagged(_)) => { + info!("render actor channel lagged"); + continue; + } + + }; + + match req { + RenderActorRequest::Render => { + let Some(document) = self.document.borrow().clone() else { + info!("PdfRenderActor: document is not ready"); + continue; + }; + + if let Some(cfg) = self.config.as_ref() { + if cfg.mode == ExportPdfMode::OnType { + self.export_pdf(&document, &cfg.path).await.unwrap(); + } + } + } + RenderActorRequest::ChangeConfig(config) => { + self.config = Some(config); + } + } + } + } + } + } + + async fn export_pdf(&self, doc: &TypstDocument, path: &Path) -> anyhow::Result<()> { + // todo: Some(pdf_uri.as_str()) + // todo: timestamp world.now() + info!("exporting PDF {path}", path = path.display()); + + let data = typst_pdf::pdf(doc, None, None); + + std::fs::write(path, data).context("failed to export PDF")?; + + info!("PDF export complete"); + + Ok(()) + } +} diff --git a/crates/tinymist/src/actor/typst.rs b/crates/tinymist/src/actor/typst.rs new file mode 100644 index 00000000..97878ab0 --- /dev/null +++ b/crates/tinymist/src/actor/typst.rs @@ -0,0 +1,1552 @@ +use std::collections::HashMap; +use std::path::Path; +use std::path::PathBuf; +use std::sync::{Arc, Mutex as SyncMutex}; + +use anyhow::anyhow; +use futures::future::join_all; +use itertools::Itertools; +use log::error; +use log::trace; +use log::warn; +use tokio::sync::broadcast; +use tokio::sync::mpsc; +use tokio::sync::watch; +use tokio::sync::Mutex; +use tokio::sync::RwLock; + +use tower_lsp::lsp_types::SelectionRange; +use tower_lsp::lsp_types::{ + CompletionResponse, DocumentSymbolResponse, Documentation, Hover, Location as LspLocation, + MarkupContent, MarkupKind, Position as LspPosition, SemanticTokens, SemanticTokensDelta, + SemanticTokensFullDeltaResult, SemanticTokensResult, SignatureHelp, SignatureInformation, + SymbolInformation, SymbolKind, TextDocumentContentChangeEvent, Url, +}; +use typst::diag::SourceDiagnostic; +use typst_ts_core::typst::prelude::EcoVec; + +use typst::diag::FileError; +use typst::diag::FileResult; +use typst::diag::SourceResult; +use typst::foundations::Func; +use typst::foundations::ParamInfo; +use typst::foundations::Value; +use typst::layout::Position; +use typst::model::Document; +use typst::syntax::ast; +use typst::syntax::ast::AstNode; +use typst::syntax::LinkedNode; +use typst::syntax::Source; +use typst::syntax::Span; +use typst::syntax::SyntaxKind; +use typst::syntax::VirtualPath; +use typst::World; +use typst_preview::CompilationHandleImpl; +use typst_ts_compiler::service::WorkspaceProvider; +use typst_ts_compiler::service::{ + CompileActor, CompileClient as TsCompileClient, CompileExporter, Compiler, WorldExporter, +}; +use typst_ts_compiler::service::{CompileDriver as CompileDriverInner, CompileMiddleware}; +use typst_ts_compiler::vfs::notify::{FileChangeSet, MemoryEvent}; +use typst_ts_compiler::NotifyApi; +use typst_ts_compiler::Time; +use typst_ts_compiler::TypstSystemWorld; +use typst_ts_core::config::CompileOpts; +use typst_ts_core::debug_loc::SourceSpanOffset; +use typst_ts_core::error::prelude::*; +use typst_ts_core::Bytes; +use typst_ts_core::DynExporter; +use typst_ts_core::Error; + +use typst_preview::{CompilationHandle, CompileStatus}; +use typst_preview::{CompileHost, EditorServer, MemoryFiles, MemoryFilesShort, SourceFileServer}; +use typst_preview::{DocToSrcJumpInfo, Location}; +use typst_ts_core::ImmutPath; +use typst_ts_core::TypstDocument; +use typst_ts_core::TypstFileId; + +use itertools::Format; +use std::iter; +use tower_lsp::lsp_types::DiagnosticRelatedInformation; +use typst::diag::{EcoString, Tracepoint}; +use typst::syntax::{FileId, Spanned}; + +use crate::analysis::analyze::analyze_expr; +use crate::config::PositionEncoding; +use crate::lsp_typst_boundary::lsp_to_typst; +use crate::lsp_typst_boundary::typst_to_lsp; +use crate::lsp_typst_boundary::LspDiagnostic; +use crate::lsp_typst_boundary::LspRange; +use crate::lsp_typst_boundary::LspRawRange; +use crate::lsp_typst_boundary::LspSeverity; +use crate::lsp_typst_boundary::TypstDiagnostic; +use crate::lsp_typst_boundary::TypstSeverity; +use crate::lsp_typst_boundary::TypstSpan; +use crate::semantic_tokens::SemanticTokenCache; +use crate::server::LspHost; + +use super::render::PdfExportActor; +use super::render::RenderActorRequest; + +type CompileService = CompileActor, H>>; +type CompileClient = TsCompileClient>; + +type DiagnosticsSender = mpsc::UnboundedSender<(String, DiagnosticsMap)>; + +type DiagnosticsMap = HashMap>; + +pub type Client = TypstClient; + +pub fn create_cluster(host: LspHost, roots: Vec, opts: CompileOpts) -> CompileCluster { + // + let (diag_tx, diag_rx) = mpsc::unbounded_channel(); + + let primary = create_server( + "primary".to_owned(), + create_compiler(roots.clone(), opts.clone()), + diag_tx, + ); + + CompileCluster { + memory_changes: RwLock::new(HashMap::new()), + primary, + semantic_tokens_delta_cache: Default::default(), + actor: Some(CompileClusterActor { + host, + diag_rx, + diagnostics: HashMap::new(), + affect_map: HashMap::new(), + }), + } +} + +fn create_compiler(roots: Vec, opts: CompileOpts) -> CompileDriver { + let world = TypstSystemWorld::new(opts).expect("incorrect options"); + CompileDriver::new(world, roots) +} + +fn create_server( + diag_group: String, + compiler_driver: CompileDriver, + diag_tx: DiagnosticsSender, +) -> CompileNode { + let (doc_sender, doc_recv) = watch::channel(None); + let (render_tx, render_rx) = broadcast::channel(1024); + + let exporter: DynExporter = Box::new(move |_w: &dyn World, doc| { + let _ = doc_sender.send(Some(doc)); // it is ok to ignore the error here + // todo: is it right that ignore zero broadcast receiver? + let _ = render_tx.send(RenderActorRequest::Render); + + Ok(()) + }); + + tokio::spawn(PdfExportActor::new(doc_recv, render_rx).run()); + + let handler: CompileHandler = compiler_driver.handler.clone(); + let compile_server = CompileServer::new( + diag_group, + compiler_driver, + handler.clone(), + diag_tx, + exporter, + ); + + CompileNode::new(handler, compile_server.spawn().unwrap()) +} + +pub struct CompileClusterActor { + host: LspHost, + diag_rx: mpsc::UnboundedReceiver<(String, DiagnosticsMap)>, + + diagnostics: HashMap>>, + affect_map: HashMap>, +} + +pub struct CompileCluster { + memory_changes: RwLock, MemoryFileMeta>>, + primary: CompileNode, + pub semantic_tokens_delta_cache: Arc>, + actor: Option, +} + +impl CompileCluster { + pub fn split(mut self) -> (Self, CompileClusterActor) { + let actor = self.actor.take().expect("actor is poisoned"); + (self, actor) + } +} + +impl CompileClusterActor { + pub async fn run(mut self) { + loop { + tokio::select! { + Some((group, diagnostics)) = self.diag_rx.recv() => { + self.publish(group, diagnostics).await; + } + } + } + } + + pub async fn publish(&mut self, group: String, next_diagnostics: DiagnosticsMap) { + let affected = self.affect_map.get_mut(&group); + + let affected = affected.map(std::mem::take); + + // Gets sources which had some diagnostic published last time, but not this + // time. The LSP specifies that files will not have diagnostics + // updated, including removed, without an explicit update, so we need + // to send an empty `Vec` of diagnostics to these sources. + let clear_list = affected + .into_iter() + .flatten() + .filter(|e| !next_diagnostics.contains_key(e)) + .map(|e| (e, None)) + .collect::>(); + let next_affected = next_diagnostics.keys().cloned().collect(); + // Gets touched updates + let update_list = next_diagnostics.into_iter().map(|(x, y)| (x, Some(y))); + + let tasks = clear_list.into_iter().chain(update_list); + let tasks = tasks.map(|(url, next)| { + let path_diags = self.diagnostics.entry(url.clone()).or_default(); + let rest_all = path_diags + .iter() + .filter_map(|(g, diags)| if g != &group { Some(diags) } else { None }) + .flatten() + .cloned(); + + let next_all = next.clone().into_iter().flatten(); + let to_publish = rest_all.chain(next_all).collect(); + + match next { + Some(next) => { + path_diags.insert(group.clone(), next); + } + None => { + path_diags.remove(&group); + } + } + + self.host.publish_diagnostics(url, to_publish, None) + }); + + join_all(tasks).await; + + // We just used the cache, and won't need it again, so we can update it now + self.affect_map.insert(group, next_affected); + } +} + +#[derive(Debug, Clone)] +struct MemoryFileMeta { + mt: Time, + content: Source, +} + +impl CompileCluster { + pub async fn create_source(&self, path: PathBuf, content: String) -> Result<(), Error> { + let now = Time::now(); + let path: ImmutPath = path.into(); + + self.memory_changes.write().await.insert( + path.clone(), + MemoryFileMeta { + mt: now, + content: Source::detached(content.clone()), + }, + ); + + let mut primary = self.primary.inner.lock().await; + + let content: Bytes = content.as_bytes().into(); + + primary.change_entry(path.clone()).await?; + // todo: is it safe to believe that the path is normalized? + let files = FileChangeSet::new_inserts(vec![(path, FileResult::Ok((now, content)).into())]); + primary + .inner() + .add_memory_changes(MemoryEvent::Update(files)); + + Ok(()) + } + + pub async fn remove_source(&self, path: PathBuf) -> Result<(), Error> { + let path: ImmutPath = path.into(); + + self.memory_changes.write().await.remove(&path); + + let mut primary = self.primary.inner.lock().await; + + // todo: is it safe to believe that the path is normalized? + let files = FileChangeSet::new_removes(vec![path]); + // todo: change focus + primary + .inner() + .add_memory_changes(MemoryEvent::Update(files)); + + Ok(()) + } + + pub async fn edit_source( + &self, + path: PathBuf, + content: Vec, + position_encoding: PositionEncoding, + ) -> Result<(), Error> { + let now = Time::now(); + let path: ImmutPath = path.into(); + + let mut memory_changes = self.memory_changes.write().await; + + let meta = memory_changes + .get_mut(&path) + .ok_or_else(|| error_once!("file missing", path: path.display()))?; + + for change in content { + let replacement = change.text; + match change.range { + Some(lsp_range) => { + let range = + LspRange::new(lsp_range, position_encoding).into_range_on(&meta.content); + meta.content.edit(range, &replacement); + } + None => { + meta.content.replace(&replacement); + } + } + } + + meta.mt = now; + + let snapshot = FileResult::Ok((now, meta.content.text().as_bytes().into())).into(); + + drop(memory_changes); + + let mut primary = self.primary.inner.lock().await; + + primary.change_entry(path.clone()).await?; + let files = FileChangeSet::new_inserts(vec![(path.clone(), snapshot)]); + primary + .inner() + .add_memory_changes(MemoryEvent::Update(files)); + + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub struct OnSaveExportRequest { + pub path: PathBuf, +} + +#[derive(Debug, Clone)] +pub struct HoverRequest { + pub path: PathBuf, + pub position: LspPosition, + pub position_encoding: PositionEncoding, +} + +#[derive(Debug, Clone)] +pub struct CompletionRequest { + pub path: PathBuf, + pub position: LspPosition, + pub position_encoding: PositionEncoding, + pub explicit: bool, +} + +#[derive(Debug, Clone)] +pub struct SignatureHelpRequest { + pub path: PathBuf, + pub position: LspPosition, + pub position_encoding: PositionEncoding, +} + +#[derive(Debug, Clone)] +pub struct DocumentSymbolRequest { + pub path: PathBuf, + pub position_encoding: PositionEncoding, +} + +#[derive(Debug, Clone)] +pub struct SymbolRequest { + pub pattern: Option, + pub position_encoding: PositionEncoding, +} + +#[derive(Debug, Clone)] +pub struct SelectionRangeRequest { + pub path: PathBuf, + pub positions: Vec, + pub position_encoding: PositionEncoding, +} + +#[derive(Debug, Clone)] +pub struct SemanticTokensFullRequest { + pub path: PathBuf, + pub position_encoding: PositionEncoding, +} + +#[derive(Debug, Clone)] +pub struct SemanticTokensDeltaRequest { + pub path: PathBuf, + pub previous_result_id: String, + pub position_encoding: PositionEncoding, +} + +#[derive(Debug, Clone)] +pub enum CompilerQueryRequest { + OnSaveExport(OnSaveExportRequest), + Hover(HoverRequest), + Completion(CompletionRequest), + SignatureHelp(SignatureHelpRequest), + DocumentSymbol(DocumentSymbolRequest), + Symbol(SymbolRequest), + SemanticTokensFull(SemanticTokensFullRequest), + SemanticTokensDelta(SemanticTokensDeltaRequest), + SelectionRange(SelectionRangeRequest), +} + +#[derive(Debug, Clone)] +pub enum CompilerQueryResponse { + OnSaveExport(()), + Hover(Option), + Completion(Option), + SignatureHelp(Option), + DocumentSymbol(Option), + Symbol(Option>), + SemanticTokensFull(Option), + SemanticTokensDelta(Option), + SelectionRange(Option>), +} + +impl CompileCluster { + pub async fn query( + &self, + query: CompilerQueryRequest, + ) -> anyhow::Result { + match query { + CompilerQueryRequest::SemanticTokensFull(SemanticTokensFullRequest { + path, + position_encoding, + }) => self + .semantic_tokens_full(path, position_encoding) + .await + .map(CompilerQueryResponse::SemanticTokensFull), + CompilerQueryRequest::SemanticTokensDelta(SemanticTokensDeltaRequest { + path, + previous_result_id, + position_encoding, + }) => self + .semantic_tokens_delta(path, previous_result_id, position_encoding) + .await + .map(CompilerQueryResponse::SemanticTokensDelta), + _ => self.primary.query(query).await, + } + } + + async fn semantic_tokens_full( + &self, + path: PathBuf, + position_encoding: PositionEncoding, + ) -> anyhow::Result> { + let path: ImmutPath = path.into(); + + let source = self + .memory_changes + .read() + .await + .get(&path) + .ok_or_else(|| anyhow!("file missing"))? + .content + .clone(); + + let (tokens, result_id) = self.get_semantic_tokens_full(&source, position_encoding); + + Ok(Some( + SemanticTokens { + result_id: Some(result_id), + data: tokens, + } + .into(), + )) + } + + async fn semantic_tokens_delta( + &self, + path: PathBuf, + previous_result_id: String, + position_encoding: PositionEncoding, + ) -> anyhow::Result> { + let path: ImmutPath = path.into(); + + let source = self + .memory_changes + .read() + .await + .get(&path) + .ok_or_else(|| anyhow!("file missing"))? + .content + .clone(); + + let (tokens, result_id) = self.try_semantic_tokens_delta_from_result_id( + &source, + &previous_result_id, + position_encoding, + ); + + Ok(match tokens { + Ok(edits) => Some( + SemanticTokensDelta { + result_id: Some(result_id), + edits, + } + .into(), + ), + Err(tokens) => Some( + SemanticTokens { + result_id: Some(result_id), + data: tokens, + } + .into(), + ), + }) + } +} + +#[derive(Clone)] +pub struct CompileHandler { + result: Arc, CompileStatus>>>, + inner: Arc>>, +} + +impl CompilationHandle for CompileHandler { + fn status(&self, status: CompileStatus) { + let inner = self.inner.lock().unwrap(); + if let Some(inner) = inner.as_ref() { + inner.status(status); + } + } + + fn notify_compile(&self, result: Result, CompileStatus>) { + *self.result.lock().unwrap() = result.clone(); + + let inner = self.inner.lock().unwrap(); + if let Some(inner) = inner.as_ref() { + inner.notify_compile(result.clone()); + } + } +} + +pub struct CompileDriver { + inner: CompileDriverInner, + roots: Vec, + handler: CompileHandler, +} + +impl CompileMiddleware for CompileDriver { + type Compiler = CompileDriverInner; + + fn inner(&self) -> &Self::Compiler { + &self.inner + } + + fn inner_mut(&mut self) -> &mut Self::Compiler { + &mut self.inner + } +} + +impl CompileDriver { + fn new(world: TypstSystemWorld, roots: Vec) -> Self { + let driver = CompileDriverInner::new(world); + + Self { + inner: driver, + roots, + handler: CompileHandler { + result: Arc::new(SyncMutex::new(Err(CompileStatus::Compiling))), + inner: Arc::new(SyncMutex::new(None)), + }, + } + } + + // todo: determine root + fn set_entry_file(&mut self, entry: PathBuf) { + let _ = &self.roots; + // let candidates = self + // .current + // .iter() + // .filter_map(|(root, package)| Some((root, + // package.uri_to_vpath(uri).ok()?))) .inspect(|(package_root, + // path)| trace!(%package_root, ?path, %uri, "considering + // candidate for full id")); + + // // Our candidates are projects containing a URI, so we expect to get + // a set of // subdirectories. The "best" is the "most + // specific", that is, the project that is a // subdirectory of + // the rest. This should have the longest length. + // let (best_package_root, best_path) = + // candidates.max_by_key(|(_, path)| + // path.as_rootless_path().components().count())?; + + // let package_id = PackageId::new_current(best_package_root.clone()); + // let full_file_id = FullFileId::new(package_id, best_path); + + self.inner.set_entry_file(entry); + } +} + +pub struct CompileServer { + inner: CompileService, + client: TypstClient, +} + +pub struct Reporter { + diag_group: String, + diag_tx: DiagnosticsSender, + inner: C, + cb: H, +} + +impl, H: CompilationHandle> CompileMiddleware + for Reporter +{ + type Compiler = C; + + fn inner(&self) -> &Self::Compiler { + &self.inner + } + + fn inner_mut(&mut self) -> &mut Self::Compiler { + &mut self.inner + } + + fn wrap_compile( + &mut self, + env: &mut typst_ts_compiler::service::CompileEnv, + ) -> SourceResult> { + self.cb.status(CompileStatus::Compiling); + match self.inner_mut().compile(env) { + Ok(doc) => { + self.cb.notify_compile(Ok(doc.clone())); + + self.push_diagnostics(EcoVec::new()); + Ok(doc) + } + Err(err) => { + self.cb.notify_compile(Err(CompileStatus::CompileError)); + + self.push_diagnostics(err); + Err(EcoVec::new()) + } + } + } +} + +impl WorldExporter for Reporter { + fn export(&mut self, output: Arc) -> SourceResult<()> { + self.inner.export(output) + } +} + +impl, H> Reporter { + fn push_diagnostics(&mut self, diagnostics: EcoVec) { + fn convert_diagnostics<'a>( + project: &TypstSystemWorld, + errors: impl IntoIterator, + position_encoding: PositionEncoding, + ) -> DiagnosticsMap { + errors + .into_iter() + .flat_map(|error| { + convert_diagnostic(project, error, position_encoding) + .map_err(move |conversion_err| { + error!("could not convert Typst error to diagnostic: {conversion_err:?} error to convert: {error:?}"); + }) + }) + .collect::>() + .into_iter() + .into_group_map() + } + + fn convert_diagnostic( + project: &TypstSystemWorld, + typst_diagnostic: &TypstDiagnostic, + position_encoding: PositionEncoding, + ) -> anyhow::Result<(Url, LspDiagnostic)> { + let uri; + let lsp_range; + if let Some((id, span)) = diagnostic_span_id(typst_diagnostic) { + uri = Url::from_file_path(project.path_for_id(id)?).unwrap(); + let source = project.source(id)?; + lsp_range = diagnostic_range(&source, span, position_encoding).raw_range; + } else { + uri = Url::from_file_path(project.root.clone()).unwrap(); + lsp_range = LspRawRange::default(); + }; + + let lsp_severity = diagnostic_severity(typst_diagnostic.severity); + + let typst_message = &typst_diagnostic.message; + let typst_hints = &typst_diagnostic.hints; + let lsp_message = format!("{typst_message}{}", diagnostic_hints(typst_hints)); + + let tracepoints = + diagnostic_related_information(project, typst_diagnostic, position_encoding)?; + + let diagnostic = LspDiagnostic { + range: lsp_range, + severity: Some(lsp_severity), + message: lsp_message, + source: Some("typst".to_owned()), + related_information: Some(tracepoints), + ..Default::default() + }; + + Ok((uri, diagnostic)) + } + + fn tracepoint_to_relatedinformation( + project: &TypstSystemWorld, + tracepoint: &Spanned, + position_encoding: PositionEncoding, + ) -> anyhow::Result> { + if let Some(id) = tracepoint.span.id() { + let uri = Url::from_file_path(project.path_for_id(id)?).unwrap(); + let source = project.source(id)?; + + if let Some(typst_range) = source.range(tracepoint.span) { + let lsp_range = typst_to_lsp::range(typst_range, &source, position_encoding); + + return Ok(Some(DiagnosticRelatedInformation { + location: LspLocation { + uri, + range: lsp_range.raw_range, + }, + message: tracepoint.v.to_string(), + })); + } + } + + Ok(None) + } + + fn diagnostic_related_information( + project: &TypstSystemWorld, + typst_diagnostic: &TypstDiagnostic, + position_encoding: PositionEncoding, + ) -> anyhow::Result> { + let mut tracepoints = vec![]; + + for tracepoint in &typst_diagnostic.trace { + if let Some(info) = + tracepoint_to_relatedinformation(project, tracepoint, position_encoding)? + { + tracepoints.push(info); + } + } + + Ok(tracepoints) + } + + fn diagnostic_span_id(typst_diagnostic: &TypstDiagnostic) -> Option<(FileId, TypstSpan)> { + iter::once(typst_diagnostic.span) + .chain(typst_diagnostic.trace.iter().map(|trace| trace.span)) + .find_map(|span| Some((span.id()?, span))) + } + + fn diagnostic_range( + source: &Source, + typst_span: TypstSpan, + position_encoding: PositionEncoding, + ) -> LspRange { + // Due to #241 and maybe typst/typst#2035, we sometimes fail to find the span. + // In that case, we use a default span as a better alternative to + // panicking. + // + // This may have been fixed after Typst 0.7.0, but it's still nice to avoid + // panics in case something similar reappears. + match source.find(typst_span) { + Some(node) => { + let typst_range = node.range(); + typst_to_lsp::range(typst_range, source, position_encoding) + } + None => LspRange::new( + LspRawRange::new(LspPosition::new(0, 0), LspPosition::new(0, 0)), + position_encoding, + ), + } + } + + fn diagnostic_severity(typst_severity: TypstSeverity) -> LspSeverity { + match typst_severity { + TypstSeverity::Error => LspSeverity::ERROR, + TypstSeverity::Warning => LspSeverity::WARNING, + } + } + + fn diagnostic_hints( + typst_hints: &[EcoString], + ) -> Format + '_> { + iter::repeat(EcoString::from("\n\nHint: ")) + .take(typst_hints.len()) + .interleave(typst_hints.iter().cloned()) + .format("") + } + + // todo encoding + let diagnostics = convert_diagnostics( + self.inner.world(), + diagnostics.as_ref(), + PositionEncoding::Utf16, + ); + + trace!("send diagnostics: {:#?}", diagnostics); + let err = self.diag_tx.send((self.diag_group.clone(), diagnostics)); + if let Err(err) = err { + error!("failed to send diagnostics: {:#}", err); + } + } +} + +impl CompileServer { + pub fn new( + diag_group: String, + compiler_driver: CompileDriver, + cb: H, + diag_tx: DiagnosticsSender, + exporter: DynExporter, + ) -> Self { + let root = compiler_driver.inner.world.root.clone(); + let driver = CompileExporter::new(compiler_driver).with_exporter(exporter); + let driver = Reporter { + diag_group, + diag_tx, + inner: driver, + cb, + }; + let inner = CompileActor::new(driver, root.as_ref().to_owned()).with_watch(true); + + Self { + inner, + client: TypstClient { + entry: Arc::new(SyncMutex::new(None)), + inner: once_cell::sync::OnceCell::new(), + }, + } + } + + pub fn spawn(self) -> Result, Error> { + let (server, client) = self.inner.split(); + tokio::spawn(server.spawn()); + + self.client.inner.set(client).ok().unwrap(); + + Ok(self.client) + } +} + +pub struct TypstClient { + entry: Arc>>, + inner: once_cell::sync::OnceCell>, +} + +// todo: remove unsafe impl send +unsafe impl Send for TypstClient {} +unsafe impl Sync for TypstClient {} + +impl TypstClient { + fn inner(&mut self) -> &mut CompileClient { + self.inner.get_mut().unwrap() + } + + /// Steal the compiler thread and run the given function. + pub async fn steal_async( + &mut self, + f: impl FnOnce(&mut CompileService, tokio::runtime::Handle) -> Ret + Send + 'static, + ) -> ZResult { + self.inner().steal_async(f).await + } + + async fn change_entry(&mut self, path: ImmutPath) -> Result<(), Error> { + if !path.is_absolute() { + return Err(error_once!("entry file must be absolute", path: path.display())); + } + + let entry = self.entry.clone(); + let should_change = { + let mut entry = entry.lock().unwrap(); + let should_change = entry.as_ref().map(|e| e != &path).unwrap_or(true); + *entry = Some(path.clone()); + should_change + }; + + if should_change { + self.steal_async(move |compiler, _| { + let root = compiler.compiler.world().workspace_root(); + if !path.starts_with(&root) { + warn!("entry file is not in workspace root {}", path.display()); + return; + } + + let driver = &mut compiler.compiler.compiler.inner.compiler; + driver.set_entry_file(path.as_ref().to_owned()); + }) + .await?; + } + + Ok(()) + } +} + +impl SourceFileServer for TypstClient { + async fn resolve_source_span( + &mut self, + loc: Location, + ) -> Result, Error> { + let Location::Src(src_loc) = loc; + self.inner().resolve_src_location(src_loc).await + } + + async fn resolve_document_position( + &mut self, + loc: Location, + ) -> Result, Error> { + let Location::Src(src_loc) = loc; + + let path = Path::new(&src_loc.filepath).to_owned(); + let line = src_loc.pos.line; + let column = src_loc.pos.column; + + self.inner() + .resolve_src_to_doc_jump(path, line, column) + .await + } + + async fn resolve_source_location( + &mut self, + s: Span, + offset: Option, + ) -> Result, Error> { + Ok(self + .inner() + .resolve_span_and_offset(s, offset) + .await + .map_err(|err| { + error!("TypstActor: failed to resolve span and offset: {:#}", err); + }) + .ok() + .flatten() + .map(|e| DocToSrcJumpInfo { + filepath: e.filepath, + start: e.start, + end: e.end, + })) + } +} + +impl EditorServer for TypstClient { + async fn update_memory_files( + &mut self, + files: MemoryFiles, + reset_shadow: bool, + ) -> Result<(), Error> { + // todo: is it safe to believe that the path is normalized? + let now = std::time::SystemTime::now(); + let files = FileChangeSet::new_inserts( + files + .files + .into_iter() + .map(|(path, content)| { + let content = content.as_bytes().into(); + // todo: cloning PathBuf -> Arc + (path.into(), Ok((now, content)).into()) + }) + .collect(), + ); + self.inner().add_memory_changes(if reset_shadow { + MemoryEvent::Sync(files) + } else { + MemoryEvent::Update(files) + }); + + Ok(()) + } + + async fn remove_shadow_files(&mut self, files: MemoryFilesShort) -> Result<(), Error> { + // todo: is it safe to believe that the path is normalized? + let files = FileChangeSet::new_removes(files.files.into_iter().map(From::from).collect()); + self.inner().add_memory_changes(MemoryEvent::Update(files)); + + Ok(()) + } +} + +impl CompileHost for TypstClient {} + +pub struct CompileNode { + handler: CompileHandler, + inner: Arc>, +} + +impl CompileNode { + fn new(handler: CompileHandler, unwrap: TypstClient) -> Self { + Self { + handler, + inner: Arc::new(Mutex::new(unwrap)), + } + } + + pub async fn query( + &self, + query: CompilerQueryRequest, + ) -> anyhow::Result { + match query { + CompilerQueryRequest::OnSaveExport(OnSaveExportRequest { path }) => { + self.on_save_export(path).await?; + Ok(CompilerQueryResponse::OnSaveExport(())) + } + CompilerQueryRequest::Hover(HoverRequest { + path, + position, + position_encoding, + }) => self + .hover(path, position, position_encoding) + .await + .map(CompilerQueryResponse::Hover), + CompilerQueryRequest::Completion(CompletionRequest { + path, + position, + position_encoding, + explicit, + }) => self + .completion(path, position, position_encoding, explicit) + .await + .map(CompilerQueryResponse::Completion), + CompilerQueryRequest::SignatureHelp(SignatureHelpRequest { + path, + position, + position_encoding, + }) => self + .signature_help(path, position, position_encoding) + .await + .map(CompilerQueryResponse::SignatureHelp), + CompilerQueryRequest::DocumentSymbol(DocumentSymbolRequest { + path, + position_encoding, + }) => self + .document_symbol(path, position_encoding) + .await + .map(CompilerQueryResponse::DocumentSymbol), + CompilerQueryRequest::Symbol(SymbolRequest { + pattern, + position_encoding, + }) => self + .symbol(pattern, position_encoding) + .await + .map(CompilerQueryResponse::Symbol), + CompilerQueryRequest::SelectionRange(SelectionRangeRequest { + path, + positions, + position_encoding, + }) => self + .selection_range(path, positions, position_encoding) + .await + .map(CompilerQueryResponse::SelectionRange), + CompilerQueryRequest::SemanticTokensDelta(..) + | CompilerQueryRequest::SemanticTokensFull(..) => unreachable!(), + } + } + + async fn on_save_export(&self, _path: PathBuf) -> anyhow::Result<()> { + Ok(()) + } + + async fn hover( + &self, + path: PathBuf, + position: LspPosition, + position_encoding: PositionEncoding, + ) -> anyhow::Result> { + let doc = self.handler.result.lock().unwrap().clone().ok(); + + let mut client = self.inner.lock().await; + let fut = client.steal_async(move |compiler, _| { + let world = compiler.compiler.world(); + + let source = get_suitable_source_in_workspace(world, &path).ok()?; + let typst_offset = + lsp_to_typst::position_to_offset(position, position_encoding, &source); + + let typst_tooltip = typst_ide::tooltip(world, doc.as_deref(), &source, typst_offset)?; + + let ast_node = LinkedNode::new(source.root()).leaf_at(typst_offset)?; + let range = typst_to_lsp::range(ast_node.range(), &source, position_encoding); + + Some(Hover { + contents: typst_to_lsp::tooltip(&typst_tooltip), + range: Some(range.raw_range), + }) + }); + + Ok(fut.await?) + } + + async fn completion( + &self, + path: PathBuf, + position: LspPosition, + position_encoding: PositionEncoding, + explicit: bool, + ) -> anyhow::Result> { + let doc = self.handler.result.lock().unwrap().clone().ok(); + + let mut client = self.inner.lock().await; + let fut = client.steal_async(move |compiler, _| { + let world = compiler.compiler.world(); + + let source = get_suitable_source_in_workspace(world, &path).ok()?; + let typst_offset = + lsp_to_typst::position_to_offset(position, position_encoding, &source); + + let (typst_start_offset, completions) = + typst_ide::autocomplete(world, doc.as_deref(), &source, typst_offset, explicit)?; + + let lsp_start_position = + typst_to_lsp::offset_to_position(typst_start_offset, position_encoding, &source); + let replace_range = LspRawRange::new(lsp_start_position, position); + Some(typst_to_lsp::completions(&completions, replace_range).into()) + }); + + Ok(fut.await?) + } + + async fn signature_help( + &self, + path: PathBuf, + position: LspPosition, + position_encoding: PositionEncoding, + ) -> anyhow::Result> { + fn surrounding_function_syntax<'b>( + leaf: &'b LinkedNode, + ) -> Option<(ast::Expr<'b>, LinkedNode<'b>, ast::Args<'b>)> { + let parent = leaf.parent()?; + let parent = match parent.kind() { + SyntaxKind::Named => parent.parent()?, + _ => parent, + }; + let args = parent.cast::()?; + let grand = parent.parent()?; + let expr = grand.cast::()?; + let callee = match expr { + ast::Expr::FuncCall(call) => call.callee(), + ast::Expr::Set(set) => set.target(), + _ => return None, + }; + Some((callee, grand.find(callee.span())?, args)) + } + + fn param_index_at_leaf( + leaf: &LinkedNode, + function: &Func, + args: ast::Args, + ) -> Option { + let deciding = deciding_syntax(leaf); + let params = function.params()?; + let param_index = find_param_index(&deciding, params, args)?; + trace!("got param index {param_index}"); + Some(param_index) + } + + /// Find the piece of syntax that decides what we're completing. + fn deciding_syntax<'b>(leaf: &'b LinkedNode) -> LinkedNode<'b> { + let mut deciding = leaf.clone(); + while !matches!( + deciding.kind(), + SyntaxKind::LeftParen | SyntaxKind::Comma | SyntaxKind::Colon + ) { + let Some(prev) = deciding.prev_leaf() else { + break; + }; + deciding = prev; + } + deciding + } + + fn find_param_index( + deciding: &LinkedNode, + params: &[ParamInfo], + args: ast::Args, + ) -> Option { + match deciding.kind() { + // After colon: "func(param:|)", "func(param: |)". + SyntaxKind::Colon => { + let prev = deciding.prev_leaf()?; + let param_ident = prev.cast::()?; + params + .iter() + .position(|param| param.name == param_ident.as_str()) + } + // Before: "func(|)", "func(hi|)", "func(12,|)". + SyntaxKind::Comma | SyntaxKind::LeftParen => { + let next = deciding.next_leaf(); + let following_param = next.as_ref().and_then(|next| next.cast::()); + match following_param { + Some(next) => params + .iter() + .position(|param| param.named && param.name.starts_with(next.as_str())), + None => { + let positional_args_so_far = args + .items() + .filter(|arg| matches!(arg, ast::Arg::Pos(_))) + .count(); + params + .iter() + .enumerate() + .filter(|(_, param)| param.positional) + .map(|(i, _)| i) + .nth(positional_args_so_far) + } + } + } + _ => None, + } + } + + fn markdown_docs(docs: &str) -> Documentation { + Documentation::MarkupContent(MarkupContent { + kind: MarkupKind::Markdown, + value: docs.to_owned(), + }) + } + + let mut client = self.inner.lock().await; + let fut = client.steal_async(move |compiler, _| { + let world = compiler.compiler.world(); + + let source = get_suitable_source_in_workspace(world, &path).ok()?; + let typst_offset = + lsp_to_typst::position_to_offset(position, position_encoding, &source); + + let ast_node = LinkedNode::new(source.root()).leaf_at(typst_offset)?; + let (callee, callee_node, args) = surrounding_function_syntax(&ast_node)?; + + let mut ancestor = &ast_node; + while !ancestor.is::() { + ancestor = ancestor.parent()?; + } + + if !callee.hash() && !matches!(callee, ast::Expr::MathIdent(_)) { + return None; + } + + let values = analyze_expr(world, &callee_node); + + let function = values.into_iter().find_map(|v| match v { + Value::Func(f) => Some(f), + _ => None, + })?; + trace!("got function {function:?}"); + + let param_index = param_index_at_leaf(&ast_node, &function, args); + + let label = format!( + "{}({}){}", + function.name().unwrap_or(""), + match function.params() { + Some(params) => params + .iter() + .map(typst_to_lsp::param_info_to_label) + .join(", "), + None => "".to_owned(), + }, + match function.returns() { + Some(returns) => format!("-> {}", typst_to_lsp::cast_info_to_label(returns)), + None => "".to_owned(), + } + ); + let params = function + .params() + .unwrap_or_default() + .iter() + .map(typst_to_lsp::param_info) + .collect(); + trace!("got signature info {label} {params:?}"); + + let documentation = function.docs().map(markdown_docs); + + let active_parameter = param_index.map(|i| i as u32); + + Some(SignatureInformation { + label, + documentation, + parameters: Some(params), + active_parameter, + }) + }); + + let signature = fut.await?; + + Ok(signature.map(|signature| SignatureHelp { + signatures: vec![signature], + active_signature: Some(0), + active_parameter: None, + })) + } + + async fn document_symbol( + &self, + path: PathBuf, + position_encoding: PositionEncoding, + ) -> anyhow::Result> { + let mut client = self.inner.lock().await; + let fut = client.steal_async(move |compiler, _| { + let world = compiler.compiler.world(); + + let source = get_suitable_source_in_workspace(world, &path).ok()?; + + let uri = Url::from_file_path(path).unwrap(); + let symbols = get_document_symbols(source, uri, position_encoding); + + symbols.map(DocumentSymbolResponse::Flat) + }); + + Ok(fut.await?) + } + + async fn symbol( + &self, + pattern: Option, + position_encoding: PositionEncoding, + ) -> anyhow::Result>> { + let mut client = self.inner.lock().await; + let fut = client.steal_async(move |compiler, _| { + let world = compiler.compiler.world(); + + // todo: expose source + + let mut symbols = vec![]; + + world.iter_dependencies(&mut |path, _| { + let Ok(source) = get_suitable_source_in_workspace(world, path) else { + return; + }; + let uri = Url::from_file_path(path).unwrap(); + let res = + get_document_symbols(source, uri, position_encoding).and_then(|symbols| { + pattern + .as_ref() + .map(|pattern| filter_document_symbols(symbols, pattern)) + }); + + if let Some(mut res) = res { + symbols.append(&mut res) + } + }); + + Some(symbols) + }); + + Ok(fut.await?) + } + + async fn selection_range( + &self, + path: PathBuf, + positions: Vec, + position_encoding: PositionEncoding, + ) -> anyhow::Result>> { + fn range_for_node( + source: &Source, + position_encoding: PositionEncoding, + node: &LinkedNode, + ) -> SelectionRange { + let range = typst_to_lsp::range(node.range(), source, position_encoding); + SelectionRange { + range: range.raw_range, + parent: node + .parent() + .map(|node| Box::new(range_for_node(source, position_encoding, node))), + } + } + + let mut client = self.inner.lock().await; + let fut = client.steal_async(move |compiler, _| { + let world = compiler.compiler.world(); + + let source = get_suitable_source_in_workspace(world, &path).ok()?; + + let mut ranges = Vec::new(); + for position in positions { + let typst_offset = + lsp_to_typst::position_to_offset(position, position_encoding, &source); + let tree = LinkedNode::new(source.root()); + let leaf = tree.leaf_at(typst_offset)?; + ranges.push(range_for_node(&source, position_encoding, &leaf)); + } + + Some(ranges) + }); + + Ok(fut.await?) + } +} + +fn get_suitable_source_in_workspace(w: &TypstSystemWorld, p: &Path) -> FileResult { + // todo: source in packages + let relative_path = p + .strip_prefix(&w.workspace_root()) + .map_err(|_| FileError::NotFound(p.to_owned()))?; + w.source(TypstFileId::new(None, VirtualPath::new(relative_path))) +} + +fn filter_document_symbols( + symbols: Vec, + query_string: &str, +) -> Vec { + symbols + .into_iter() + .filter(|e| e.name.contains(query_string)) + .collect() +} + +#[comemo::memoize] +fn get_document_symbols( + source: Source, + uri: Url, + position_encoding: PositionEncoding, +) -> Option> { + struct DocumentSymbolWorker { + symbols: Vec, + } + + impl DocumentSymbolWorker { + /// Get all symbols for a node recursively. + pub fn get_symbols<'a>( + &mut self, + node: LinkedNode<'a>, + source: &'a Source, + uri: &'a Url, + position_encoding: PositionEncoding, + ) -> anyhow::Result<()> { + let own_symbol = get_ident(&node, source, uri, position_encoding)?; + + for child in node.children() { + self.get_symbols(child, source, uri, position_encoding)?; + } + + if let Some(symbol) = own_symbol { + self.symbols.push(symbol); + } + + Ok(()) + } + } + + /// Get symbol for a leaf node of a valid type, or `None` if the node is an + /// invalid type. + #[allow(deprecated)] + fn get_ident( + node: &LinkedNode, + source: &Source, + uri: &Url, + position_encoding: PositionEncoding, + ) -> anyhow::Result> { + match node.kind() { + SyntaxKind::Label => { + let ast_node = node + .cast::() + .ok_or_else(|| anyhow!("cast to ast node failed: {:?}", node))?; + let name = ast_node.get().to_string(); + let symbol = SymbolInformation { + name, + kind: SymbolKind::CONSTANT, + tags: None, + deprecated: None, // do not use, deprecated, use `tags` instead + location: LspLocation { + uri: uri.clone(), + range: typst_to_lsp::range(node.range(), source, position_encoding) + .raw_range, + }, + container_name: None, + }; + Ok(Some(symbol)) + } + SyntaxKind::Ident => { + let ast_node = node + .cast::() + .ok_or_else(|| anyhow!("cast to ast node failed: {:?}", node))?; + let name = ast_node.get().to_string(); + let Some(parent) = node.parent() else { + return Ok(None); + }; + let kind = match parent.kind() { + // for variable definitions, the Let binding holds an Ident + SyntaxKind::LetBinding => SymbolKind::VARIABLE, + // for function definitions, the Let binding holds a Closure which holds the + // Ident + SyntaxKind::Closure => { + let Some(grand_parent) = parent.parent() else { + return Ok(None); + }; + match grand_parent.kind() { + SyntaxKind::LetBinding => SymbolKind::FUNCTION, + _ => return Ok(None), + } + } + _ => return Ok(None), + }; + let symbol = SymbolInformation { + name, + kind, + tags: None, + deprecated: None, // do not use, deprecated, use `tags` instead + location: LspLocation { + uri: uri.clone(), + range: typst_to_lsp::range(node.range(), source, position_encoding) + .raw_range, + }, + container_name: None, + }; + Ok(Some(symbol)) + } + SyntaxKind::Markup => { + let name = node.get().to_owned().into_text().to_string(); + if name.is_empty() { + return Ok(None); + } + let Some(parent) = node.parent() else { + return Ok(None); + }; + let kind = match parent.kind() { + SyntaxKind::Heading => SymbolKind::NAMESPACE, + _ => return Ok(None), + }; + let symbol = SymbolInformation { + name, + kind, + tags: None, + deprecated: None, // do not use, deprecated, use `tags` instead + location: LspLocation { + uri: uri.clone(), + range: typst_to_lsp::range(node.range(), source, position_encoding) + .raw_range, + }, + container_name: None, + }; + Ok(Some(symbol)) + } + _ => Ok(None), + } + } + + let root = LinkedNode::new(source.root()); + + let mut worker = DocumentSymbolWorker { symbols: vec![] }; + + let res = worker + .get_symbols(root, &source, &uri, position_encoding) + .ok(); + + res.map(|_| worker.symbols) +} diff --git a/crates/tinymist/src/analysis/analyze.rs b/crates/tinymist/src/analysis/analyze.rs new file mode 100644 index 00000000..d8e93639 --- /dev/null +++ b/crates/tinymist/src/analysis/analyze.rs @@ -0,0 +1,107 @@ +use comemo::Track; +use typst::diag::EcoString; +use typst::engine::{Engine, Route}; +use typst::eval::{Tracer, Vm}; +use typst::foundations::{Label, Scopes, Value}; +use typst::introspection::{Introspector, Locator}; +use typst::model::{BibliographyElem, Document}; +use typst::syntax::{ast, LinkedNode, Span, SyntaxKind}; +use typst::World; +use typst_ts_core::typst::prelude::{eco_vec, EcoVec}; + +/// Try to determine a set of possible values for an expression. +pub fn analyze_expr(world: &dyn World, node: &LinkedNode) -> EcoVec { + match node.cast::() { + Some(ast::Expr::None(_)) => eco_vec![Value::None], + Some(ast::Expr::Auto(_)) => eco_vec![Value::Auto], + Some(ast::Expr::Bool(v)) => eco_vec![Value::Bool(v.get())], + Some(ast::Expr::Int(v)) => eco_vec![Value::Int(v.get())], + Some(ast::Expr::Float(v)) => eco_vec![Value::Float(v.get())], + Some(ast::Expr::Numeric(v)) => eco_vec![Value::numeric(v.get())], + Some(ast::Expr::Str(v)) => eco_vec![Value::Str(v.get().into())], + + Some(ast::Expr::FieldAccess(access)) => { + let Some(child) = node.children().next() else { + return eco_vec![]; + }; + analyze_expr(world, &child) + .into_iter() + .filter_map(|target| target.field(&access.field()).ok()) + .collect() + } + + Some(_) => { + if let Some(parent) = node.parent() { + if parent.kind() == SyntaxKind::FieldAccess && node.index() > 0 { + return analyze_expr(world, parent); + } + } + + let mut tracer = Tracer::new(); + tracer.inspect(node.span()); + typst::compile(world, &mut tracer).ok(); + tracer.values() + } + + _ => eco_vec![], + } +} + +/// Try to load a module from the current source file. +pub fn analyze_import(world: &dyn World, source: &LinkedNode) -> Option { + let source = analyze_expr(world, source).into_iter().next()?; + if source.scope().is_some() { + return Some(source); + } + + let mut locator = Locator::default(); + let introspector = Introspector::default(); + let mut tracer = Tracer::new(); + let engine = Engine { + world: world.track(), + route: Route::default(), + introspector: introspector.track(), + locator: &mut locator, + tracer: tracer.track_mut(), + }; + + let mut vm = Vm::new(engine, Scopes::new(Some(world.library())), Span::detached()); + typst::eval::import(&mut vm, source, Span::detached(), true) + .ok() + .map(Value::Module) +} + +/// Find all labels and details for them. +/// +/// Returns: +/// - All labels and descriptions for them, if available +/// - A split offset: All labels before this offset belong to nodes, all after +/// belong to a bibliography. +pub fn analyze_labels(document: &Document) -> (Vec<(Label, Option)>, usize) { + let mut output = vec![]; + + // Labels in the document. + for elem in document.introspector.all() { + let Some(label) = elem.label() else { continue }; + let details = elem + .get_by_name("caption") + .or_else(|| elem.get_by_name("body")) + .and_then(|field| match field { + Value::Content(content) => Some(content), + _ => None, + }) + .as_ref() + .unwrap_or(elem) + .plain_text(); + output.push((label, Some(details))); + } + + let split = output.len(); + + // Bibliography keys. + for (key, detail) in BibliographyElem::keys(document.introspector.track()) { + output.push((Label::new(&key), detail)); + } + + (output, split) +} diff --git a/crates/tinymist/src/analysis/mod.rs b/crates/tinymist/src/analysis/mod.rs new file mode 100644 index 00000000..92d19eb5 --- /dev/null +++ b/crates/tinymist/src/analysis/mod.rs @@ -0,0 +1 @@ +pub mod analyze; diff --git a/crates/tinymist/src/config.rs b/crates/tinymist/src/config.rs new file mode 100644 index 00000000..5c0ae342 --- /dev/null +++ b/crates/tinymist/src/config.rs @@ -0,0 +1,234 @@ +use std::{fmt, path::PathBuf}; + +use anyhow::bail; +use futures::future::BoxFuture; +use itertools::Itertools; +use serde::Deserialize; +use serde_json::{Map, Value}; +use tower_lsp::lsp_types::{ + self, ConfigurationItem, InitializeParams, PositionEncodingKind, Registration, +}; + +use crate::ext::InitializeParamsExt; + +const CONFIG_REGISTRATION_ID: &str = "config"; +const CONFIG_METHOD_ID: &str = "workspace/didChangeConfiguration"; + +pub fn get_config_registration() -> Registration { + Registration { + id: CONFIG_REGISTRATION_ID.to_owned(), + method: CONFIG_METHOD_ID.to_owned(), + register_options: None, + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum ExperimentalFormatterMode { + #[default] + Off, + On, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum ExportPdfMode { + Never, + #[default] + OnSave, + OnType, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum SemanticTokensMode { + Disable, + #[default] + Enable, +} + +pub type Listener = Box BoxFuture> + Send + Sync>; + +const CONFIG_ITEMS: &[&str] = &[ + "exportPdf", + "rootPath", + "semanticTokens", + "experimentalFormatterMode", +]; + +#[derive(Default)] +pub struct Config { + pub export_pdf: ExportPdfMode, + pub root_path: Option, + pub semantic_tokens: SemanticTokensMode, + pub formatter: ExperimentalFormatterMode, + semantic_tokens_listeners: Vec>, + formatter_listeners: Vec>, +} + +impl Config { + pub fn get_items() -> Vec { + let sections = CONFIG_ITEMS + .iter() + .flat_map(|item| [format!("tinymist.{item}"), item.to_string()]); + + sections + .map(|section| ConfigurationItem { + section: Some(section), + ..Default::default() + }) + .collect() + } + + pub fn values_to_map(values: Vec) -> Map { + let unpaired_values = values + .into_iter() + .tuples() + .map(|(a, b)| if !a.is_null() { a } else { b }); + + CONFIG_ITEMS + .iter() + .map(|item| item.to_string()) + .zip(unpaired_values) + .collect() + } + + pub fn listen_semantic_tokens(&mut self, listener: Listener) { + self.semantic_tokens_listeners.push(listener); + } + + // pub fn listen_formatting(&mut self, listener: + // Listener) { self.formatter_listeners. + // push(listener); } + + pub async fn update(&mut self, update: &Value) -> anyhow::Result<()> { + if let Value::Object(update) = update { + self.update_by_map(update).await + } else { + bail!("got invalid configuration object {update}") + } + } + + pub async fn update_by_map(&mut self, update: &Map) -> anyhow::Result<()> { + let export_pdf = update + .get("exportPdf") + .map(ExportPdfMode::deserialize) + .and_then(Result::ok); + if let Some(export_pdf) = export_pdf { + self.export_pdf = export_pdf; + } + + let root_path = update.get("rootPath"); + if let Some(root_path) = root_path { + if root_path.is_null() { + self.root_path = None; + } + if let Some(root_path) = root_path.as_str().map(PathBuf::from) { + self.root_path = Some(root_path); + } + } + + let semantic_tokens = update + .get("semanticTokens") + .map(SemanticTokensMode::deserialize) + .and_then(Result::ok); + if let Some(semantic_tokens) = semantic_tokens { + for listener in &mut self.semantic_tokens_listeners { + listener(&semantic_tokens).await?; + } + self.semantic_tokens = semantic_tokens; + } + + let formatter = update + .get("experimentalFormatterMode") + .map(ExperimentalFormatterMode::deserialize) + .and_then(Result::ok); + if let Some(formatter) = formatter { + for listener in &mut self.formatter_listeners { + listener(&formatter).await?; + } + self.formatter = formatter; + } + + Ok(()) + } +} + +impl fmt::Debug for Config { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Config") + .field("export_pdf", &self.export_pdf) + .field("formatter", &self.formatter) + .field("semantic_tokens", &self.semantic_tokens) + .field( + "semantic_tokens_listeners", + &format_args!("Vec[len = {}]", self.semantic_tokens_listeners.len()), + ) + .field( + "formatter_listeners", + &format_args!("Vec[len = {}]", self.formatter_listeners.len()), + ) + .finish() + } +} + +/// What counts as "1 character" for string indexing. We should always prefer +/// UTF-8, but support UTF-16 as long as it is standard. For more background on +/// encodings and LSP, try ["The bottom emoji breaks rust-analyzer"](https://fasterthanli.me/articles/the-bottom-emoji-breaks-rust-analyzer), +/// a well-written article on the topic. +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default)] +pub enum PositionEncoding { + /// "1 character" means "1 UTF-16 code unit" + /// + /// This is the only required encoding for LSPs to support, but it's not a + /// natural one (unless you're working in JS). Prefer UTF-8, and refer + /// to the article linked in the `PositionEncoding` docs for more + /// background. + #[default] + Utf16, + /// "1 character" means "1 byte" + Utf8, +} + +impl From for lsp_types::PositionEncodingKind { + fn from(position_encoding: PositionEncoding) -> Self { + match position_encoding { + PositionEncoding::Utf16 => Self::UTF16, + PositionEncoding::Utf8 => Self::UTF8, + } + } +} + +/// Configuration set at initialization that won't change within a single +/// session +#[derive(Debug)] +pub struct ConstConfig { + pub position_encoding: PositionEncoding, + pub supports_semantic_tokens_dynamic_registration: bool, + pub supports_document_formatting_dynamic_registration: bool, + pub supports_config_change_registration: bool, +} + +impl ConstConfig { + fn choose_encoding(params: &InitializeParams) -> PositionEncoding { + let encodings = params.position_encodings(); + if encodings.contains(&PositionEncodingKind::UTF8) { + PositionEncoding::Utf8 + } else { + PositionEncoding::Utf16 + } + } +} + +impl From<&InitializeParams> for ConstConfig { + fn from(params: &InitializeParams) -> Self { + Self { + position_encoding: Self::choose_encoding(params), + supports_semantic_tokens_dynamic_registration: params + .supports_semantic_tokens_dynamic_registration(), + supports_document_formatting_dynamic_registration: params + .supports_document_formatting_dynamic_registration(), + supports_config_change_registration: params.supports_config_change_registration(), + } + } +} diff --git a/crates/tinymist/src/ext.rs b/crates/tinymist/src/ext.rs new file mode 100644 index 00000000..5b268b06 --- /dev/null +++ b/crates/tinymist/src/ext.rs @@ -0,0 +1,141 @@ +use std::ffi::OsStr; +use std::path::PathBuf; + +use tower_lsp::lsp_types::{DocumentFormattingClientCapabilities, Url}; +use tower_lsp::lsp_types::{ + InitializeParams, Position, PositionEncodingKind, SemanticTokensClientCapabilities, +}; +use typst::syntax::VirtualPath; + +use crate::config::PositionEncoding; + +pub trait InitializeParamsExt { + fn position_encodings(&self) -> &[PositionEncodingKind]; + fn supports_config_change_registration(&self) -> bool; + fn semantic_tokens_capabilities(&self) -> Option<&SemanticTokensClientCapabilities>; + fn document_formatting_capabilities(&self) -> Option<&DocumentFormattingClientCapabilities>; + fn supports_semantic_tokens_dynamic_registration(&self) -> bool; + fn supports_document_formatting_dynamic_registration(&self) -> bool; + fn root_paths(&self) -> Vec; +} + +static DEFAULT_ENCODING: [PositionEncodingKind; 1] = [PositionEncodingKind::UTF16]; + +impl InitializeParamsExt for InitializeParams { + fn position_encodings(&self) -> &[PositionEncodingKind] { + self.capabilities + .general + .as_ref() + .and_then(|general| general.position_encodings.as_ref()) + .map(|encodings| encodings.as_slice()) + .unwrap_or(&DEFAULT_ENCODING) + } + + fn supports_config_change_registration(&self) -> bool { + self.capabilities + .workspace + .as_ref() + .and_then(|workspace| workspace.configuration) + .unwrap_or(false) + } + + fn semantic_tokens_capabilities(&self) -> Option<&SemanticTokensClientCapabilities> { + self.capabilities + .text_document + .as_ref()? + .semantic_tokens + .as_ref() + } + + fn document_formatting_capabilities(&self) -> Option<&DocumentFormattingClientCapabilities> { + self.capabilities + .text_document + .as_ref()? + .formatting + .as_ref() + } + + fn supports_semantic_tokens_dynamic_registration(&self) -> bool { + self.semantic_tokens_capabilities() + .and_then(|semantic_tokens| semantic_tokens.dynamic_registration) + .unwrap_or(false) + } + + fn supports_document_formatting_dynamic_registration(&self) -> bool { + self.document_formatting_capabilities() + .and_then(|document_format| document_format.dynamic_registration) + .unwrap_or(false) + } + + #[allow(deprecated)] // `self.root_path` is marked as deprecated + fn root_paths(&self) -> Vec { + match self.workspace_folders.as_ref() { + Some(roots) => roots + .iter() + .map(|root| &root.uri) + .map(Url::to_file_path) + .collect::, _>>() + .unwrap(), + None => self + .root_uri + .as_ref() + .map(|uri| uri.to_file_path().unwrap()) + .or_else(|| self.root_path.clone().map(PathBuf::from)) + .into_iter() + .collect(), + } + } +} + +pub trait StrExt { + fn encoded_len(&self, encoding: PositionEncoding) -> usize; +} + +impl StrExt for str { + fn encoded_len(&self, encoding: PositionEncoding) -> usize { + match encoding { + PositionEncoding::Utf8 => self.len(), + PositionEncoding::Utf16 => self.chars().map(char::len_utf16).sum(), + } + } +} + +pub trait VirtualPathExt { + fn with_extension(&self, extension: impl AsRef) -> Self; +} + +impl VirtualPathExt for VirtualPath { + fn with_extension(&self, extension: impl AsRef) -> Self { + Self::new(self.as_rooted_path().with_extension(extension)) + } +} + +pub trait PositionExt { + fn delta(&self, to: &Self) -> PositionDelta; +} + +impl PositionExt for Position { + /// Calculates the delta from `self` to `to`. This is in the `SemanticToken` + /// sense, so the delta's `character` is relative to `self`'s + /// `character` iff `self` and `to` are on the same line. Otherwise, + /// it's relative to the start of the line `to` is on. + fn delta(&self, to: &Self) -> PositionDelta { + let line_delta = to.line - self.line; + let char_delta = if line_delta == 0 { + to.character - self.character + } else { + to.character + }; + + PositionDelta { + delta_line: line_delta, + delta_start: char_delta, + } + } +} + +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Default)] +pub struct PositionDelta { + pub delta_line: u32, + pub delta_start: u32, +} diff --git a/crates/tinymist/src/lsp.rs b/crates/tinymist/src/lsp.rs new file mode 100644 index 00000000..9092a71a --- /dev/null +++ b/crates/tinymist/src/lsp.rs @@ -0,0 +1,636 @@ +use std::borrow::Cow; + +use anyhow::Context; +use async_trait::async_trait; +use futures::FutureExt; +use log::{error, info, trace}; +use serde_json::Value as JsonValue; +use tower_lsp::lsp_types::*; +use tower_lsp::{jsonrpc, LanguageServer}; +use typst_ts_core::config::CompileOpts; + +use crate::actor; +use crate::actor::typst::{ + CompilerQueryResponse, CompletionRequest, DocumentSymbolRequest, HoverRequest, + OnSaveExportRequest, SelectionRangeRequest, SemanticTokensDeltaRequest, + SemanticTokensFullRequest, SignatureHelpRequest, SymbolRequest, +}; +use crate::config::{ + get_config_registration, Config, ConstConfig, ExperimentalFormatterMode, ExportPdfMode, + SemanticTokensMode, +}; +use crate::ext::InitializeParamsExt; +// use crate::server::formatting::{get_formatting_registration, +// get_formatting_unregistration}; +// use crate::workspace::Workspace; + +use super::semantic_tokens::{ + get_semantic_tokens_options, get_semantic_tokens_registration, + get_semantic_tokens_unregistration, +}; +use super::TypstServer; + +macro_rules! run_query { + ($self: expr, $query: ident, $req: expr) => {{ + let req = $req; + $self + .universe() + .query(actor::typst::CompilerQueryRequest::$query(req.clone())) + .await + .map_err(|err| { + error!("error getting $query: {err} with request {req:?}"); + jsonrpc::Error::internal_error() + }) + .map(|resp| { + let CompilerQueryResponse::$query(resp) = resp else { + unreachable!() + }; + resp + }) + }}; +} + +#[async_trait] +impl LanguageServer for TypstServer { + async fn initialize(&self, params: InitializeParams) -> jsonrpc::Result { + // self.tracing_init(); + + let cluster = { + let root_paths = params.root_paths(); + let primary_root = root_paths.first().cloned().unwrap_or_default(); + actor::typst::create_cluster( + self.client.clone(), + root_paths, + CompileOpts { + root_dir: primary_root, + // todo: font paths + // font_paths: arguments.font_paths.clone(), + with_embedded_fonts: typst_assets::fonts().map(Cow::Borrowed).collect(), + ..CompileOpts::default() + }, + ) + }; + + let (cluster, cluster_bg) = cluster.split(); + + self.universe + .set(cluster) + .map_err(|_| ()) + .expect("the cluster is already initialized"); + + self.const_config + .set(ConstConfig::from(¶ms)) + .expect("const config should not yet be initialized"); + + tokio::spawn(cluster_bg.run()); + + if let Some(init) = ¶ms.initialization_options { + let mut config = self.config.write().await; + config + .update(init) + .await + .as_ref() + .map_err(ToString::to_string) + .map_err(jsonrpc::Error::invalid_params)?; + } + + let config = self.config.read().await; + + let semantic_tokens_provider = match config.semantic_tokens { + SemanticTokensMode::Enable + if !params.supports_semantic_tokens_dynamic_registration() => + { + Some(get_semantic_tokens_options().into()) + } + _ => None, + }; + + let document_formatting_provider = match config.formatter { + ExperimentalFormatterMode::On + if !params.supports_document_formatting_dynamic_registration() => + { + Some(OneOf::Left(true)) + } + _ => None, + }; + + Ok(InitializeResult { + capabilities: ServerCapabilities { + signature_help_provider: Some(SignatureHelpOptions { + trigger_characters: Some(vec!["(".to_string(), ",".to_string()]), + retrigger_characters: None, + work_done_progress_options: WorkDoneProgressOptions { + work_done_progress: None, + }, + }), + hover_provider: Some(HoverProviderCapability::Simple(true)), + completion_provider: Some(CompletionOptions { + trigger_characters: Some(vec![ + String::from("#"), + String::from("."), + String::from("@"), + ]), + ..Default::default() + }), + text_document_sync: Some(TextDocumentSyncCapability::Options( + TextDocumentSyncOptions { + open_close: Some(true), + change: Some(TextDocumentSyncKind::INCREMENTAL), + save: Some(TextDocumentSyncSaveOptions::Supported(true)), + ..Default::default() + }, + )), + semantic_tokens_provider, + execute_command_provider: Some(ExecuteCommandOptions { + commands: LspCommand::all_as_string(), + work_done_progress_options: WorkDoneProgressOptions { + work_done_progress: None, + }, + }), + document_symbol_provider: Some(OneOf::Left(true)), + workspace_symbol_provider: Some(OneOf::Left(true)), + selection_range_provider: Some(SelectionRangeProviderCapability::Simple(true)), + workspace: Some(WorkspaceServerCapabilities { + workspace_folders: Some(WorkspaceFoldersServerCapabilities { + supported: Some(true), + change_notifications: Some(OneOf::Left(true)), + }), + ..Default::default() + }), + document_formatting_provider, + ..Default::default() + }, + ..Default::default() + }) + } + + async fn initialized(&self, _: InitializedParams) { + let const_config = self.const_config(); + let mut config = self.config.write().await; + + if const_config.supports_semantic_tokens_dynamic_registration { + trace!("setting up to dynamically register semantic token support"); + + let client = self.client.clone(); + let register = move || { + trace!("dynamically registering semantic tokens"); + let client = client.clone(); + async move { + let options = get_semantic_tokens_options(); + client + .register_capability(vec![get_semantic_tokens_registration(options)]) + .await + .context("could not register semantic tokens") + } + }; + + let client = self.client.clone(); + let unregister = move || { + trace!("unregistering semantic tokens"); + let client = client.clone(); + async move { + client + .unregister_capability(vec![get_semantic_tokens_unregistration()]) + .await + .context("could not unregister semantic tokens") + } + }; + + if config.semantic_tokens == SemanticTokensMode::Enable { + if let Some(err) = register().await.err() { + error!("could not dynamically register semantic tokens: {err}"); + } + } + + config.listen_semantic_tokens(Box::new(move |mode| match mode { + SemanticTokensMode::Enable => register().boxed(), + SemanticTokensMode::Disable => unregister().boxed(), + })); + } + + // if const_config.supports_document_formatting_dynamic_registration { + // trace!("setting up to dynamically register document formatting support"); + + // let client = self.client.clone(); + // let register = move || { + // trace!("dynamically registering document formatting"); + // let client = client.clone(); + // async move { + // client + // .register_capability(vec![get_formatting_registration()]) + // .await + // .context("could not register document formatting") + // } + // }; + + // let client = self.client.clone(); + // let unregister = move || { + // trace!("unregistering document formatting"); + // let client = client.clone(); + // async move { + // client + // .unregister_capability(vec![get_formatting_unregistration()]) + // .await + // .context("could not unregister document formatting") + // } + // }; + + // if config.formatter == ExperimentalFormatterMode::On { + // if let Some(err) = register().await.err() { + // error!("could not dynamically register document formatting: + // {err}"); } + // } + + // config.listen_formatting(Box::new(move |formatter| match formatter { + // ExperimentalFormatterMode::On => register().boxed(), + // ExperimentalFormatterMode::Off => unregister().boxed(), + // })); + // } + + if const_config.supports_config_change_registration { + trace!("setting up to request config change notifications"); + + let err = self + .client + .register_capability(vec![get_config_registration()]) + .await + .err(); + if let Some(err) = err { + error!("could not register to watch config changes: {err}"); + } + } + + // trace!("setting up to watch Typst files"); + // let watch_files_error = self + // .client + // .register_capability(vec![self.get_watcher_registration()]) + // .await + // .err(); + // if let Some(err) = watch_files_error { + // error!("could not register to watch Typst files: {err}"); + // } + + info!("server initialized"); + } + + async fn shutdown(&self) -> jsonrpc::Result<()> { + Ok(()) + } + + async fn did_open(&self, params: DidOpenTextDocumentParams) { + let path = params.text_document.uri.to_file_path().unwrap(); + let text = params.text_document.text; + + let universe = self.universe(); + universe.create_source(path.clone(), text).await.unwrap(); + } + + async fn did_close(&self, params: DidCloseTextDocumentParams) { + let path = params.text_document.uri.to_file_path().unwrap(); + + let universe = self.universe(); + universe.remove_source(path.clone()).await.unwrap(); + // self.client.publish_diagnostics(uri, Vec::new(), None).await; + } + + async fn did_change(&self, params: DidChangeTextDocumentParams) { + let path = params.text_document.uri.to_file_path().unwrap(); + let changes = params.content_changes; + + let universe = self.universe(); + universe + .edit_source(path.clone(), changes, self.const_config().position_encoding) + .await + .unwrap(); + } + + async fn did_save(&self, params: DidSaveTextDocumentParams) { + let uri = params.text_document.uri; + let path = uri.to_file_path().unwrap(); + + let config = self.config.read().await; + + if config.export_pdf == ExportPdfMode::OnSave { + let _ = run_query!(self, OnSaveExport, OnSaveExportRequest { path }); + } + } + + // async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) + // { let changes = params.changes; + + // let mut workspace = self.workspace().write().await; + + // for change in changes { + // self.handle_file_change_event(&mut workspace, change); + // } + // } + + // async fn did_change_workspace_folders(&self, params: + // DidChangeWorkspaceFoldersParams) { let event = params.event; + + // let mut workspace = self.workspace().write().await; + + // if let Err(err) = workspace.handle_workspace_folders_change_event(&event) + // { error!("error when changing workspace folders: {err}"); + // } + // } + + async fn execute_command( + &self, + params: ExecuteCommandParams, + ) -> jsonrpc::Result> { + let ExecuteCommandParams { + command, + arguments, + work_done_progress_params: _, + } = params; + match LspCommand::parse(&command) { + Some(LspCommand::ExportPdf) => { + self.command_export_pdf(arguments).await?; + } + Some(LspCommand::ClearCache) => { + self.command_clear_cache(arguments).await?; + } + None => { + error!("asked to execute unknown command"); + return Err(jsonrpc::Error::method_not_found()); + } + }; + Ok(None) + } + + async fn hover(&self, params: HoverParams) -> jsonrpc::Result> { + let uri = ¶ms.text_document_position_params.text_document.uri; + let path = uri.to_file_path().unwrap(); + let position = params.text_document_position_params.position; + let position_encoding = self.const_config().position_encoding; + + run_query!( + self, + Hover, + HoverRequest { + path, + position, + position_encoding, + } + ) + } + + async fn completion( + &self, + params: CompletionParams, + ) -> jsonrpc::Result> { + let uri = params.text_document_position.text_document.uri; + let path = uri.to_file_path().unwrap(); + let position = params.text_document_position.position; + let explicit = params + .context + .map(|context| context.trigger_kind == CompletionTriggerKind::INVOKED) + .unwrap_or(false); + let position_encoding = self.const_config().position_encoding; + + run_query!( + self, + Completion, + CompletionRequest { + path, + position, + position_encoding, + explicit, + } + ) + } + + async fn signature_help( + &self, + params: SignatureHelpParams, + ) -> jsonrpc::Result> { + let uri = params.text_document_position_params.text_document.uri; + let path = uri.to_file_path().unwrap(); + let position = params.text_document_position_params.position; + let position_encoding = self.const_config().position_encoding; + + run_query!( + self, + SignatureHelp, + SignatureHelpRequest { + path, + position, + position_encoding, + } + ) + } + + async fn document_symbol( + &self, + params: DocumentSymbolParams, + ) -> jsonrpc::Result> { + let uri = params.text_document.uri; + let path = uri.to_file_path().unwrap(); + let position_encoding = self.const_config().position_encoding; + + run_query!( + self, + DocumentSymbol, + DocumentSymbolRequest { + path, + position_encoding + } + ) + } + + async fn symbol( + &self, + params: WorkspaceSymbolParams, + ) -> jsonrpc::Result>> { + let pattern = (!params.query.is_empty()).then_some(params.query); + let position_encoding = self.const_config().position_encoding; + + run_query!( + self, + Symbol, + SymbolRequest { + pattern, + position_encoding + } + ) + } + + async fn selection_range( + &self, + params: SelectionRangeParams, + ) -> jsonrpc::Result>> { + let uri = params.text_document.uri; + let path = uri.to_file_path().unwrap(); + let positions = params.positions; + let position_encoding = self.const_config().position_encoding; + + run_query!( + self, + SelectionRange, + SelectionRangeRequest { + path, + positions, + position_encoding + } + ) + } + + async fn semantic_tokens_full( + &self, + params: SemanticTokensParams, + ) -> jsonrpc::Result> { + let uri = params.text_document.uri; + let path = uri.to_file_path().unwrap(); + let position_encoding = self.const_config().position_encoding; + + run_query!( + self, + SemanticTokensFull, + SemanticTokensFullRequest { + path, + position_encoding + } + ) + } + + async fn semantic_tokens_full_delta( + &self, + params: SemanticTokensDeltaParams, + ) -> jsonrpc::Result> { + let uri = params.text_document.uri; + let path = uri.to_file_path().unwrap(); + let previous_result_id = params.previous_result_id; + let position_encoding = self.const_config().position_encoding; + + run_query!( + self, + SemanticTokensDelta, + SemanticTokensDeltaRequest { + path, + previous_result_id, + position_encoding + } + ) + } + + async fn did_change_configuration(&self, params: DidChangeConfigurationParams) { + // For some clients, we don't get the actual changed configuration and need to + // poll for it https://github.com/microsoft/language-server-protocol/issues/676 + let values = match params.settings { + JsonValue::Object(settings) => Ok(settings), + _ => self + .client + .configuration(Config::get_items()) + .await + .map(Config::values_to_map), + }; + + let result = match values { + Ok(values) => { + let mut config = self.config.write().await; + config.update_by_map(&values).await + } + Err(err) => Err(err.into()), + }; + + match result { + Ok(()) => { + info!("new settings applied"); + } + Err(err) => { + error!("error applying new settings: {err}"); + } + } + } + + // async fn formatting( + // &self, + // params: DocumentFormattingParams, + // ) -> jsonrpc::Result>> { + // let uri = params.text_document.uri; + + // let edits = self + // .scope_with_source(&uri) + // .await + // .map_err(|err| { + // error!("error getting document to format: {err} {uri}"); + // jsonrpc::Error::internal_error() + // })? + // .run2(|source, project| self.format_document(project, source)) + // .await + // .map_err(|err| { + // error!("error formatting document: {err} {uri}"); + // jsonrpc::Error::internal_error() + // })?; + + // Ok(Some(edits)) + // } +} +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum LspCommand { + ExportPdf, + ClearCache, +} + +impl From for String { + fn from(command: LspCommand) -> Self { + match command { + LspCommand::ExportPdf => "tinymist.doPdfExport".to_string(), + LspCommand::ClearCache => "tinymist.doClearCache".to_string(), + } + } +} + +impl LspCommand { + pub fn parse(command: &str) -> Option { + match command { + "tinymist.doPdfExport" => Some(Self::ExportPdf), + "tinymist.doClearCache" => Some(Self::ClearCache), + _ => None, + } + } + + pub fn all_as_string() -> Vec { + vec![Self::ExportPdf.into(), Self::ClearCache.into()] + } +} + +/// Here are implemented the handlers for each command. +impl TypstServer { + /// Export the current document as a PDF file. The client is responsible for + /// passing the correct file URI. + pub async fn command_export_pdf(&self, arguments: Vec) -> jsonrpc::Result<()> { + if arguments.is_empty() { + return Err(jsonrpc::Error::invalid_params("Missing file URI argument")); + } + let Some(file_uri) = arguments.first().and_then(|v| v.as_str()) else { + return Err(jsonrpc::Error::invalid_params( + "Missing file URI as first argument", + )); + }; + let file_uri = Url::parse(file_uri) + .map_err(|_| jsonrpc::Error::invalid_params("Parameter is not a valid URI"))?; + let path = file_uri + .to_file_path() + .map_err(|_| jsonrpc::Error::invalid_params("URI is not a file URI"))?; + + let _ = run_query!(self, OnSaveExport, OnSaveExportRequest { path }); + + Ok(()) + } + + /// Clear all cached resources. + pub async fn command_clear_cache(&self, _arguments: Vec) -> jsonrpc::Result<()> { + // self.workspace().write().await.clear().map_err(|err| { + // error!("could not clear cache: {err}"); + // jsonrpc::Error::internal_error() + // })?; + + // self.typst(|_| comemo::evict(0)).await; + + // Ok(()) + + todo!() + } +} diff --git a/crates/tinymist/src/lsp_typst_boundary.rs b/crates/tinymist/src/lsp_typst_boundary.rs new file mode 100644 index 00000000..e4249f7e --- /dev/null +++ b/crates/tinymist/src/lsp_typst_boundary.rs @@ -0,0 +1,382 @@ +//! Conversions between Typst and LSP types and representations + +use tower_lsp::lsp_types; +use typst::syntax::Source; + +pub type LspPosition = lsp_types::Position; +/// The interpretation of an `LspCharacterOffset` depends on the +/// `LspPositionEncoding` +pub type LspCharacterOffset = u32; +pub type LspPositionEncoding = crate::config::PositionEncoding; +/// Byte offset (i.e. UTF-8 bytes) in Typst files, either from the start of the +/// line or the file +pub type TypstOffset = usize; +pub type TypstSpan = typst::syntax::Span; + +/// An LSP range. It needs its associated `LspPositionEncoding` to be used. The +/// `LspRange` struct provides this range with that encoding. +pub type LspRawRange = lsp_types::Range; +pub type TypstRange = std::ops::Range; + +pub type TypstTooltip = typst_ide::Tooltip; +pub type LspHoverContents = lsp_types::HoverContents; + +pub type LspDiagnostic = lsp_types::Diagnostic; +pub type TypstDiagnostic = typst::diag::SourceDiagnostic; + +pub type LspSeverity = lsp_types::DiagnosticSeverity; +pub type TypstSeverity = typst::diag::Severity; + +pub type LspParamInfo = lsp_types::ParameterInformation; +pub type TypstParamInfo = typst::foundations::ParamInfo; + +/// An LSP range with its associated encoding. +pub struct LspRange { + pub raw_range: LspRawRange, + pub encoding: LspPositionEncoding, +} + +impl LspRange { + pub fn new(raw_range: LspRawRange, encoding: LspPositionEncoding) -> Self { + Self { + raw_range, + encoding, + } + } + + pub fn into_range_on(self, source: &Source) -> TypstRange { + lsp_to_typst::range(&self, source) + } +} + +pub type LspCompletion = lsp_types::CompletionItem; +pub type LspCompletionKind = lsp_types::CompletionItemKind; +pub type TypstCompletion = typst_ide::Completion; +pub type TypstCompletionKind = typst_ide::CompletionKind; + +pub mod lsp_to_typst { + use typst::syntax::Source; + + use super::*; + + pub fn position_to_offset( + lsp_position: LspPosition, + lsp_position_encoding: LspPositionEncoding, + typst_source: &Source, + ) -> TypstOffset { + match lsp_position_encoding { + LspPositionEncoding::Utf8 => { + let line_index = lsp_position.line as usize; + let column_index = lsp_position.character as usize; + typst_source + .line_column_to_byte(line_index, column_index) + .unwrap() + } + LspPositionEncoding::Utf16 => { + // We have a line number and a UTF-16 offset into that line. We want a byte + // offset into the file. + // + // Typst's `Source` provides several UTF-16 methods: + // - `len_utf16` for the length of the file + // - `byte_to_utf16` to convert a byte offset from the start of the file to a + // UTF-16 offset from the start of the file + // - `utf16_to_byte` to do the opposite of `byte_to_utf16` + // + // Unfortunately, none of these address our needs well, so we do some math + // instead. This is not the fastest possible implementation, but + // it's the most reasonable without access to the internal state + // of `Source`. + + // TODO: Typst's `Source` could easily provide an implementation of the method + // we need here. Submit a PR against `typst` to add it, then + // update this if/when merged. + + let line_index = lsp_position.line as usize; + let utf16_offset_in_line = lsp_position.character as usize; + + let byte_line_offset = typst_source.line_to_byte(line_index).unwrap(); + let utf16_line_offset = typst_source.byte_to_utf16(byte_line_offset).unwrap(); + let utf16_offset = utf16_line_offset + utf16_offset_in_line; + + typst_source.utf16_to_byte(utf16_offset).unwrap() + } + } + } + + pub fn range(lsp_range: &LspRange, source: &Source) -> TypstRange { + let lsp_start = lsp_range.raw_range.start; + let typst_start = position_to_offset(lsp_start, lsp_range.encoding, source); + + let lsp_end = lsp_range.raw_range.end; + let typst_end = position_to_offset(lsp_end, lsp_range.encoding, source); + + TypstRange { + start: typst_start, + end: typst_end, + } + } +} + +pub mod typst_to_lsp { + + use itertools::Itertools; + use lazy_static::lazy_static; + use regex::{Captures, Regex}; + use tower_lsp::lsp_types::{ + CompletionTextEdit, Documentation, InsertTextFormat, LanguageString, MarkedString, + MarkupContent, MarkupKind, TextEdit, + }; + use typst::diag::EcoString; + use typst::foundations::{CastInfo, Repr}; + use typst::syntax::Source; + + use super::*; + + pub fn offset_to_position( + typst_offset: TypstOffset, + lsp_position_encoding: LspPositionEncoding, + typst_source: &Source, + ) -> LspPosition { + let line_index = typst_source.byte_to_line(typst_offset).unwrap(); + let column_index = typst_source.byte_to_column(typst_offset).unwrap(); + + let lsp_line = line_index as u32; + let lsp_column = match lsp_position_encoding { + LspPositionEncoding::Utf8 => column_index as LspCharacterOffset, + LspPositionEncoding::Utf16 => { + // See the implementation of `lsp_to_typst::position_to_offset` for discussion + // relevant to this function. + + // TODO: Typst's `Source` could easily provide an implementation of the method + // we need here. Submit a PR to `typst` to add it, then update + // this if/when merged. + + let utf16_offset = typst_source.byte_to_utf16(typst_offset).unwrap(); + + let byte_line_offset = typst_source.line_to_byte(line_index).unwrap(); + let utf16_line_offset = typst_source.byte_to_utf16(byte_line_offset).unwrap(); + + let utf16_column_offset = utf16_offset - utf16_line_offset; + utf16_column_offset as LspCharacterOffset + } + }; + + LspPosition::new(lsp_line, lsp_column) + } + + pub fn range( + typst_range: TypstRange, + typst_source: &Source, + lsp_position_encoding: LspPositionEncoding, + ) -> LspRange { + let typst_start = typst_range.start; + let lsp_start = offset_to_position(typst_start, lsp_position_encoding, typst_source); + + let typst_end = typst_range.end; + let lsp_end = offset_to_position(typst_end, lsp_position_encoding, typst_source); + + let raw_range = LspRawRange::new(lsp_start, lsp_end); + LspRange::new(raw_range, lsp_position_encoding) + } + + fn completion_kind(typst_completion_kind: TypstCompletionKind) -> LspCompletionKind { + match typst_completion_kind { + TypstCompletionKind::Syntax => LspCompletionKind::SNIPPET, + TypstCompletionKind::Func => LspCompletionKind::FUNCTION, + TypstCompletionKind::Param => LspCompletionKind::VARIABLE, + TypstCompletionKind::Constant => LspCompletionKind::CONSTANT, + TypstCompletionKind::Symbol(_) => LspCompletionKind::TEXT, + TypstCompletionKind::Type => LspCompletionKind::CLASS, + } + } + + lazy_static! { + static ref TYPST_SNIPPET_PLACEHOLDER_RE: Regex = Regex::new(r"\$\{(.*?)\}").unwrap(); + } + + /// Adds numbering to placeholders in snippets + fn snippet(typst_snippet: &EcoString) -> String { + let mut counter = 1; + let result = + TYPST_SNIPPET_PLACEHOLDER_RE.replace_all(typst_snippet.as_str(), |cap: &Captures| { + let substitution = format!("${{{}:{}}}", counter, &cap[1]); + counter += 1; + substitution + }); + + result.to_string() + } + + pub fn completion( + typst_completion: &TypstCompletion, + lsp_replace: LspRawRange, + ) -> LspCompletion { + let typst_snippet = typst_completion + .apply + .as_ref() + .unwrap_or(&typst_completion.label); + let lsp_snippet = snippet(typst_snippet); + let text_edit = CompletionTextEdit::Edit(TextEdit::new(lsp_replace, lsp_snippet)); + + LspCompletion { + label: typst_completion.label.to_string(), + kind: Some(completion_kind(typst_completion.kind.clone())), + detail: typst_completion.detail.as_ref().map(String::from), + text_edit: Some(text_edit), + insert_text_format: Some(InsertTextFormat::SNIPPET), + ..Default::default() + } + } + + pub fn completions( + typst_completions: &[TypstCompletion], + lsp_replace: LspRawRange, + ) -> Vec { + typst_completions + .iter() + .map(|typst_completion| completion(typst_completion, lsp_replace)) + .collect_vec() + } + + pub fn tooltip(typst_tooltip: &TypstTooltip) -> LspHoverContents { + let lsp_marked_string = match typst_tooltip { + TypstTooltip::Text(text) => MarkedString::String(text.to_string()), + TypstTooltip::Code(code) => MarkedString::LanguageString(LanguageString { + language: "typst".to_owned(), + value: code.to_string(), + }), + }; + LspHoverContents::Scalar(lsp_marked_string) + } + + pub fn param_info(typst_param_info: &TypstParamInfo) -> LspParamInfo { + LspParamInfo { + label: lsp_types::ParameterLabel::Simple(typst_param_info.name.to_owned()), + documentation: param_info_to_docs(typst_param_info), + } + } + + pub fn param_info_to_label(typst_param_info: &TypstParamInfo) -> String { + format!( + "{}: {}", + typst_param_info.name, + cast_info_to_label(&typst_param_info.input) + ) + } + + fn param_info_to_docs(typst_param_info: &TypstParamInfo) -> Option { + if !typst_param_info.docs.is_empty() { + Some(Documentation::MarkupContent(MarkupContent { + value: typst_param_info.docs.to_owned(), + kind: MarkupKind::Markdown, + })) + } else { + None + } + } + + pub fn cast_info_to_label(cast_info: &CastInfo) -> String { + match cast_info { + CastInfo::Any => "any".to_owned(), + CastInfo::Value(value, _) => value.repr().to_string(), + CastInfo::Type(ty) => ty.to_string(), + CastInfo::Union(options) => options.iter().map(cast_info_to_label).join(" "), + } + } +} + +#[cfg(test)] +mod test { + use typst::syntax::Source; + + use crate::config::PositionEncoding; + use crate::lsp_typst_boundary::lsp_to_typst; + + use super::*; + + const ENCODING_TEST_STRING: &str = "test 🥺 test"; + + #[test] + fn utf16_position_to_utf8_offset() { + let source = Source::detached(ENCODING_TEST_STRING); + + let start = LspPosition { + line: 0, + character: 0, + }; + let emoji = LspPosition { + line: 0, + character: 5, + }; + let post_emoji = LspPosition { + line: 0, + character: 7, + }; + let end = LspPosition { + line: 0, + character: 12, + }; + + let start_offset = + lsp_to_typst::position_to_offset(start, PositionEncoding::Utf16, &source); + let start_actual = 0; + + let emoji_offset = + lsp_to_typst::position_to_offset(emoji, PositionEncoding::Utf16, &source); + let emoji_actual = 5; + + let post_emoji_offset = + lsp_to_typst::position_to_offset(post_emoji, PositionEncoding::Utf16, &source); + let post_emoji_actual = 9; + + let end_offset = lsp_to_typst::position_to_offset(end, PositionEncoding::Utf16, &source); + let end_actual = 14; + + assert_eq!(start_offset, start_actual); + assert_eq!(emoji_offset, emoji_actual); + assert_eq!(post_emoji_offset, post_emoji_actual); + assert_eq!(end_offset, end_actual); + } + + #[test] + fn utf8_offset_to_utf16_position() { + let source = Source::detached(ENCODING_TEST_STRING); + + let start = 0; + let emoji = 5; + let post_emoji = 9; + let end = 14; + + let start_position = LspPosition { + line: 0, + character: 0, + }; + let start_actual = + typst_to_lsp::offset_to_position(start, PositionEncoding::Utf16, &source); + + let emoji_position = LspPosition { + line: 0, + character: 5, + }; + let emoji_actual = + typst_to_lsp::offset_to_position(emoji, PositionEncoding::Utf16, &source); + + let post_emoji_position = LspPosition { + line: 0, + character: 7, + }; + let post_emoji_actual = + typst_to_lsp::offset_to_position(post_emoji, PositionEncoding::Utf16, &source); + + let end_position = LspPosition { + line: 0, + character: 12, + }; + let end_actual = typst_to_lsp::offset_to_position(end, PositionEncoding::Utf16, &source); + + assert_eq!(start_position, start_actual); + assert_eq!(emoji_position, emoji_actual); + assert_eq!(post_emoji_position, post_emoji_actual); + assert_eq!(end_position, end_actual); + } +} diff --git a/crates/tinymist/src/main.rs b/crates/tinymist/src/main.rs new file mode 100644 index 00000000..432587c3 --- /dev/null +++ b/crates/tinymist/src/main.rs @@ -0,0 +1,60 @@ +//! # tinymist LSP Server + +mod config; +mod ext; +mod lsp_typst_boundary; +mod server; + +// pub mod formatting; +pub mod actor; +pub mod analysis; +pub mod lsp; +pub mod semantic_tokens; + +use server::TypstServer; + +use tower_lsp::{LspService, Server}; + +// #[derive(Debug, Clone)] +// struct Args {} + +// fn arg_parser() -> OptionParser { +// construct!(Args {}).to_options().version( +// format!( +// "{}, commit {} (Typst version {TYPST_VERSION})", +// env!("CARGO_PKG_VERSION"), +// env!("GIT_COMMIT") +// ) +// .as_str(), +// ) +// } + +// pub const TYPST_VERSION: &str = env!("TYPST_VERSION"); + +#[tokio::main] +async fn main() { + let _ = env_logger::builder() + // TODO: set this back to Info + .filter_module("tinymist", log::LevelFilter::Trace) + // .filter_module("tinymist", log::LevelFilter::Debug) + .filter_module("typst_preview", log::LevelFilter::Debug) + .filter_module("typst_ts", log::LevelFilter::Info) + // TODO: set this back to Info + .filter_module( + "typst_ts_compiler::service::compile", + log::LevelFilter::Debug, + ) + .filter_module("typst_ts_compiler::service::watch", log::LevelFilter::Debug) + .try_init(); + + run().await; +} + +async fn run() { + let stdin = tokio::io::stdin(); + let stdout = tokio::io::stdout(); + + let (service, socket) = LspService::new(TypstServer::new); + + Server::new(stdin, stdout, socket).serve(service).await; +} diff --git a/crates/tinymist/src/semantic_tokens/delta.rs b/crates/tinymist/src/semantic_tokens/delta.rs new file mode 100644 index 00000000..2eaf978a --- /dev/null +++ b/crates/tinymist/src/semantic_tokens/delta.rs @@ -0,0 +1,75 @@ +use tower_lsp::lsp_types::{SemanticToken, SemanticTokensEdit}; + +#[derive(Debug)] +struct CachedTokens { + tokens: Vec, + id: u64, +} + +#[derive(Default, Debug)] +pub struct Cache { + last_sent: Option, + next_id: u64, +} + +impl Cache { + pub fn try_take_result(&mut self, id: &str) -> Option> { + let id = id.parse::().ok()?; + match self.last_sent.take() { + Some(cached) if cached.id == id => Some(cached.tokens), + Some(cached) => { + // replace after taking + self.last_sent = Some(cached); + None + } + None => None, + } + } + + pub fn cache_result(&mut self, tokens: Vec) -> String { + let id = self.get_next_id(); + let cached = CachedTokens { tokens, id }; + self.last_sent = Some(cached); + id.to_string() + } + + fn get_next_id(&mut self) -> u64 { + let id = self.next_id; + self.next_id += 1; + id + } +} + +pub fn token_delta(from: &[SemanticToken], to: &[SemanticToken]) -> Vec { + // Taken from `rust-analyzer`'s algorithm + // https://github.com/rust-lang/rust-analyzer/blob/master/crates/rust-analyzer/src/semantic_tokens.rs#L219 + + let start = from + .iter() + .zip(to.iter()) + .take_while(|(x, y)| x == y) + .count(); + + let (_, from) = from.split_at(start); + let (_, to) = to.split_at(start); + + let dist_from_end = from + .iter() + .rev() + .zip(to.iter().rev()) + .take_while(|(x, y)| x == y) + .count(); + + let (from, _) = from.split_at(from.len() - dist_from_end); + let (to, _) = to.split_at(to.len() - dist_from_end); + + if from.is_empty() && to.is_empty() { + vec![] + } else { + vec![SemanticTokensEdit { + start: 5 * start as u32, + delete_count: 5 * from.len() as u32, + data: Some(to.into()), + }] + } +} diff --git a/crates/tinymist/src/semantic_tokens/mod.rs b/crates/tinymist/src/semantic_tokens/mod.rs new file mode 100644 index 00000000..140357b7 --- /dev/null +++ b/crates/tinymist/src/semantic_tokens/mod.rs @@ -0,0 +1,238 @@ +use itertools::Itertools; +use strum::IntoEnumIterator; +use tower_lsp::lsp_types::{ + Registration, SemanticToken, SemanticTokensEdit, SemanticTokensFullOptions, + SemanticTokensLegend, SemanticTokensOptions, Unregistration, +}; +use typst::diag::EcoString; +use typst::syntax::{ast, LinkedNode, Source, SyntaxKind}; + +use crate::actor::typst::CompileCluster; +use crate::config::PositionEncoding; + +use self::delta::token_delta; +use self::modifier_set::ModifierSet; +use self::token_encode::encode_tokens; +use self::typst_tokens::{Modifier, TokenType}; + +pub use self::delta::Cache as SemanticTokenCache; + +mod delta; +mod modifier_set; +mod token_encode; +mod typst_tokens; + +pub fn get_legend() -> SemanticTokensLegend { + SemanticTokensLegend { + token_types: TokenType::iter().map(Into::into).collect(), + token_modifiers: Modifier::iter().map(Into::into).collect(), + } +} + +const SEMANTIC_TOKENS_REGISTRATION_ID: &str = "semantic_tokens"; +const SEMANTIC_TOKENS_METHOD_ID: &str = "textDocument/semanticTokens"; + +pub fn get_semantic_tokens_registration(options: SemanticTokensOptions) -> Registration { + Registration { + id: SEMANTIC_TOKENS_REGISTRATION_ID.to_owned(), + method: SEMANTIC_TOKENS_METHOD_ID.to_owned(), + register_options: Some( + serde_json::to_value(options) + .expect("semantic tokens options should be representable as JSON value"), + ), + } +} + +pub fn get_semantic_tokens_unregistration() -> Unregistration { + Unregistration { + id: SEMANTIC_TOKENS_REGISTRATION_ID.to_owned(), + method: SEMANTIC_TOKENS_METHOD_ID.to_owned(), + } +} + +pub fn get_semantic_tokens_options() -> SemanticTokensOptions { + SemanticTokensOptions { + legend: get_legend(), + full: Some(SemanticTokensFullOptions::Delta { delta: Some(true) }), + ..Default::default() + } +} + +impl CompileCluster { + pub fn get_semantic_tokens_full( + &self, + source: &Source, + encoding: PositionEncoding, + ) -> (Vec, String) { + let root = LinkedNode::new(source.root()); + + let tokens = tokenize_tree(&root, ModifierSet::empty()); + let encoded_tokens = encode_tokens(tokens, source, encoding); + let output_tokens = encoded_tokens.map(|(token, _)| token).collect_vec(); + + let result_id = self + .semantic_tokens_delta_cache + .write() + .cache_result(output_tokens.clone()); + + (output_tokens, result_id) + } + + pub fn try_semantic_tokens_delta_from_result_id( + &self, + source: &Source, + result_id: &str, + encoding: PositionEncoding, + ) -> (Result, Vec>, String) { + let cached = self + .semantic_tokens_delta_cache + .write() + .try_take_result(result_id); + + // this call will overwrite the cache, so need to read from cache first + let (tokens, result_id) = self.get_semantic_tokens_full(source, encoding); + + match cached { + Some(cached) => (Ok(token_delta(&cached, &tokens)), result_id), + None => (Err(tokens), result_id), + } + } +} + +fn tokenize_single_node(node: &LinkedNode, modifiers: ModifierSet) -> Option { + let is_leaf = node.children().next().is_none(); + + token_from_node(node) + .or_else(|| is_leaf.then_some(TokenType::Text)) + .map(|token_type| Token::new(token_type, modifiers, node)) +} + +/// Tokenize a node and its children +fn tokenize_tree<'a>( + root: &LinkedNode<'a>, + parent_modifiers: ModifierSet, +) -> Box + 'a> { + let modifiers = parent_modifiers | modifiers_from_node(root); + + let token = tokenize_single_node(root, modifiers).into_iter(); + let children = root + .children() + .flat_map(move |child| tokenize_tree(&child, modifiers)); + Box::new(token.chain(children)) +} + +pub struct Token { + pub token_type: TokenType, + pub modifiers: ModifierSet, + pub offset: usize, + pub source: EcoString, +} + +impl Token { + pub fn new(token_type: TokenType, modifiers: ModifierSet, node: &LinkedNode) -> Self { + let source = node.get().clone().into_text(); + + Self { + token_type, + modifiers, + offset: node.offset(), + source, + } + } +} + +/// Determines the [`Modifier`]s to be applied to a node and all its children. +/// +/// Note that this does not recurse up, so calling it on a child node may not +/// return a modifier that should be applied to it due to a parent. +fn modifiers_from_node(node: &LinkedNode) -> ModifierSet { + match node.kind() { + SyntaxKind::Emph => ModifierSet::new(&[Modifier::Emph]), + SyntaxKind::Strong => ModifierSet::new(&[Modifier::Strong]), + SyntaxKind::Math | SyntaxKind::Equation => ModifierSet::new(&[Modifier::Math]), + _ => ModifierSet::empty(), + } +} + +/// Determines the best [`TokenType`] for an entire node and its children, if +/// any. If there is no single `TokenType`, or none better than `Text`, returns +/// `None`. +/// +/// In tokenization, returning `Some` stops recursion, while returning `None` +/// continues and attempts to tokenize each of `node`'s children. If there are +/// no children, `Text` is taken as the default. +fn token_from_node(node: &LinkedNode) -> Option { + use SyntaxKind::*; + + match node.kind() { + Star if node.parent_kind() == Some(Strong) => Some(TokenType::Punctuation), + Star if node.parent_kind() == Some(ModuleImport) => Some(TokenType::Operator), + + Underscore if node.parent_kind() == Some(Emph) => Some(TokenType::Punctuation), + Underscore if node.parent_kind() == Some(MathAttach) => Some(TokenType::Operator), + + MathIdent | Ident => Some(token_from_ident(node)), + Hash => token_from_hashtag(node), + + LeftBrace | RightBrace | LeftBracket | RightBracket | LeftParen | RightParen | Comma + | Semicolon | Colon => Some(TokenType::Punctuation), + Linebreak | Escape | Shorthand => Some(TokenType::Escape), + Link => Some(TokenType::Link), + Raw => Some(TokenType::Raw), + Label => Some(TokenType::Label), + RefMarker => Some(TokenType::Ref), + Heading | HeadingMarker => Some(TokenType::Heading), + ListMarker | EnumMarker | TermMarker => Some(TokenType::ListMarker), + MathAlignPoint | Plus | Minus | Slash | Hat | Dot | Eq | EqEq | ExclEq | Lt | LtEq | Gt + | GtEq | PlusEq | HyphEq | StarEq | SlashEq | Dots | Arrow | Not | And | Or => { + Some(TokenType::Operator) + } + Dollar => Some(TokenType::Delimiter), + None | Auto | Let | Show | If | Else | For | In | While | Break | Continue | Return + | Import | Include | As | Set => Some(TokenType::Keyword), + Bool => Some(TokenType::Bool), + Int | Float | Numeric => Some(TokenType::Number), + Str => Some(TokenType::String), + LineComment | BlockComment => Some(TokenType::Comment), + Error => Some(TokenType::Error), + + // Disambiguate from `SyntaxKind::None` + _ => Option::None, + } +} + +// TODO: differentiate also using tokens in scope, not just context +fn is_function_ident(ident: &LinkedNode) -> bool { + let Some(next) = ident.next_leaf() else { + return false; + }; + let function_call = matches!(next.kind(), SyntaxKind::LeftParen) + && matches!( + next.parent_kind(), + Some(SyntaxKind::Args | SyntaxKind::Params) + ); + let function_content = matches!(next.kind(), SyntaxKind::LeftBracket) + && matches!(next.parent_kind(), Some(SyntaxKind::ContentBlock)); + function_call || function_content +} + +fn token_from_ident(ident: &LinkedNode) -> TokenType { + if is_function_ident(ident) { + TokenType::Function + } else { + TokenType::Interpolated + } +} + +fn get_expr_following_hashtag<'a>(hashtag: &LinkedNode<'a>) -> Option> { + hashtag + .next_sibling() + .filter(|next| next.cast::().map_or(false, |expr| expr.hash())) + .and_then(|node| node.leftmost_leaf()) +} + +fn token_from_hashtag(hashtag: &LinkedNode) -> Option { + get_expr_following_hashtag(hashtag) + .as_ref() + .and_then(token_from_node) +} diff --git a/crates/tinymist/src/semantic_tokens/modifier_set.rs b/crates/tinymist/src/semantic_tokens/modifier_set.rs new file mode 100644 index 00000000..f214da11 --- /dev/null +++ b/crates/tinymist/src/semantic_tokens/modifier_set.rs @@ -0,0 +1,33 @@ +use std::ops; + +use super::typst_tokens::Modifier; + +#[derive(Default, Clone, Copy)] +pub struct ModifierSet(u32); + +impl ModifierSet { + pub fn empty() -> Self { + Self::default() + } + + pub fn new(modifiers: &[Modifier]) -> Self { + let bits = modifiers + .iter() + .copied() + .map(Modifier::bitmask) + .fold(0, |bits, mask| bits | mask); + Self(bits) + } + + pub fn bitset(self) -> u32 { + self.0 + } +} + +impl ops::BitOr for ModifierSet { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self::Output { + Self(self.0 | rhs.0) + } +} diff --git a/crates/tinymist/src/semantic_tokens/token_encode.rs b/crates/tinymist/src/semantic_tokens/token_encode.rs new file mode 100644 index 00000000..455f2bf8 --- /dev/null +++ b/crates/tinymist/src/semantic_tokens/token_encode.rs @@ -0,0 +1,44 @@ +use tower_lsp::lsp_types::{Position, SemanticToken}; +use typst::diag::EcoString; +use typst::syntax::Source; + +use crate::config::PositionEncoding; +use crate::ext::{PositionExt, StrExt}; +use crate::lsp_typst_boundary::typst_to_lsp; + +use super::Token; + +pub(super) fn encode_tokens<'a>( + tokens: impl Iterator + 'a, + source: &'a Source, + encoding: PositionEncoding, +) -> impl Iterator + 'a { + tokens.scan(Position::new(0, 0), move |last_position, token| { + let (encoded_token, source_code, position) = + encode_token(token, last_position, source, encoding); + *last_position = position; + Some((encoded_token, source_code)) + }) +} + +fn encode_token( + token: Token, + last_position: &Position, + source: &Source, + encoding: PositionEncoding, +) -> (SemanticToken, EcoString, Position) { + let position = typst_to_lsp::offset_to_position(token.offset, encoding, source); + let delta = last_position.delta(&position); + + let length = token.source.as_str().encoded_len(encoding); + + let lsp_token = SemanticToken { + delta_line: delta.delta_line, + delta_start: delta.delta_start, + length: length as u32, + token_type: token.token_type as u32, + token_modifiers_bitset: token.modifiers.bitset(), + }; + + (lsp_token, token.source, position) +} diff --git a/crates/tinymist/src/semantic_tokens/typst_tokens.rs b/crates/tinymist/src/semantic_tokens/typst_tokens.rs new file mode 100644 index 00000000..821b12b0 --- /dev/null +++ b/crates/tinymist/src/semantic_tokens/typst_tokens.rs @@ -0,0 +1,133 @@ +//! Types for tokens used for Typst syntax + +use strum::EnumIter; +use tower_lsp::lsp_types::{SemanticTokenModifier, SemanticTokenType}; + +const BOOL: SemanticTokenType = SemanticTokenType::new("bool"); +const PUNCTUATION: SemanticTokenType = SemanticTokenType::new("punct"); +const ESCAPE: SemanticTokenType = SemanticTokenType::new("escape"); +const LINK: SemanticTokenType = SemanticTokenType::new("link"); +const RAW: SemanticTokenType = SemanticTokenType::new("raw"); +const LABEL: SemanticTokenType = SemanticTokenType::new("label"); +const REF: SemanticTokenType = SemanticTokenType::new("ref"); +const HEADING: SemanticTokenType = SemanticTokenType::new("heading"); +const LIST_MARKER: SemanticTokenType = SemanticTokenType::new("marker"); +const LIST_TERM: SemanticTokenType = SemanticTokenType::new("term"); +const DELIMITER: SemanticTokenType = SemanticTokenType::new("delim"); +const INTERPOLATED: SemanticTokenType = SemanticTokenType::new("pol"); +const ERROR: SemanticTokenType = SemanticTokenType::new("error"); +const TEXT: SemanticTokenType = SemanticTokenType::new("text"); + +/// Very similar to [`typst_ide::Tag`], but with convenience traits, and +/// extensible because we want to further customize highlighting +#[derive(Clone, Copy, EnumIter)] +#[repr(u32)] +pub enum TokenType { + // Standard LSP types + Comment, + String, + Keyword, + Operator, + Number, + Function, + Decorator, + // Custom types + Bool, + Punctuation, + Escape, + Link, + Raw, + Label, + Ref, + Heading, + ListMarker, + ListTerm, + Delimiter, + Interpolated, + Error, + /// Any text in markup without a more specific token type, possible styled. + /// + /// We perform styling (like bold and italics) via modifiers. That means + /// everything that should receive styling needs to be a token so we can + /// apply a modifier to it. This token type is mostly for that, since + /// text should usually not be specially styled. + Text, +} + +impl From for SemanticTokenType { + fn from(token_type: TokenType) -> Self { + use TokenType::*; + + match token_type { + Comment => Self::COMMENT, + String => Self::STRING, + Keyword => Self::KEYWORD, + Operator => Self::OPERATOR, + Number => Self::NUMBER, + Function => Self::FUNCTION, + Decorator => Self::DECORATOR, + Bool => BOOL, + Punctuation => PUNCTUATION, + Escape => ESCAPE, + Link => LINK, + Raw => RAW, + Label => LABEL, + Ref => REF, + Heading => HEADING, + ListMarker => LIST_MARKER, + ListTerm => LIST_TERM, + Delimiter => DELIMITER, + Interpolated => INTERPOLATED, + Error => ERROR, + Text => TEXT, + } + } +} + +const STRONG: SemanticTokenModifier = SemanticTokenModifier::new("strong"); +const EMPH: SemanticTokenModifier = SemanticTokenModifier::new("emph"); +const MATH: SemanticTokenModifier = SemanticTokenModifier::new("math"); + +#[derive(Clone, Copy, EnumIter)] +#[repr(u8)] +pub enum Modifier { + Strong, + Emph, + Math, +} + +impl Modifier { + pub fn index(self) -> u8 { + self as u8 + } + + pub fn bitmask(self) -> u32 { + 0b1 << self.index() + } +} + +impl From for SemanticTokenModifier { + fn from(modifier: Modifier) -> Self { + use Modifier::*; + + match modifier { + Strong => STRONG, + Emph => EMPH, + Math => MATH, + } + } +} + +#[cfg(test)] +mod test { + use strum::IntoEnumIterator; + + use super::*; + + #[test] + fn ensure_not_too_many_modifiers() { + // Because modifiers are encoded in a 32 bit bitmask, we can't have more than 32 + // modifiers + assert!(Modifier::iter().len() <= 32); + } +} diff --git a/crates/tinymist/src/server.rs b/crates/tinymist/src/server.rs new file mode 100644 index 00000000..8232d73c --- /dev/null +++ b/crates/tinymist/src/server.rs @@ -0,0 +1,158 @@ +use std::sync::Arc; + +use once_cell::sync::OnceCell; +use tokio::sync::{Mutex, RwLock}; +pub use tower_lsp::Client as LspHost; +use typst::model::Document; + +use crate::actor::typst::CompileCluster; +use crate::config::{Config, ConstConfig}; + +pub struct TypstServer { + pub client: LspHost, + pub document: Mutex>, + // typst_thread: TypstThread, + pub universe: OnceCell, + pub config: Arc>, + pub const_config: OnceCell, +} + +impl TypstServer { + pub fn new(client: LspHost) -> Self { + Self { + // typst_thread: Default::default(), + universe: Default::default(), + config: Default::default(), + const_config: Default::default(), + client, + document: Default::default(), + } + } + + pub fn const_config(&self) -> &ConstConfig { + self.const_config + .get() + .expect("const config should be initialized") + } + + pub fn universe(&self) -> &CompileCluster { + self.universe.get().expect("universe should be initialized") + } + + // pub fn typst_global_scopes(&self) -> typst::foundations::Scopes { + // typst::foundations::Scopes::new(Some(&TYPST_STDLIB)) + // } + + // pub async fn register_workspace_files(&self) -> FsResult<()> { + // let mut workspace = self.workspace().write().await; + // workspace.register_files() + // } + + // async fn read_workspace(&self) -> RwLockReadGuard { + // self.workspace().read().await + // } + + // async fn read_workspace_owned(&self) -> OwnedRwLockReadGuard { + // Arc::clone(self.workspace()).read_owned().await + // } + + // pub async fn project_and_full_id(&self, uri: &Url) -> FsResult<(Project, + // FullFileId)> { let workspace = self.read_workspace_owned().await; + // let full_id = workspace.full_id(uri)?; + // let project = Project::new(full_id.package(), workspace); + // Ok((project, full_id)) + // } + + // pub async fn scope_with_source(&self, uri: &Url) -> FsResult { + // let (project, _) = self.project_and_full_id(uri).await?; + // let source = project.read_source_by_uri(uri)?; + // Ok(SourceScope { project, source }) + // } + + // pub async fn thread_with_world( + // &self, + // builder: impl Into>, + // ) -> FsResult { + // let (main, project) = + // builder.into().main_project(self.workspace()).await?; + + // Ok(WorldThread { + // main, + // main_project: project, + // typst_thread: &self.typst_thread, + // }) + // } + + // /// Run the given function on the Typst thread, passing back its return + // /// value. + // pub async fn typst( + // &self, + // f: impl FnOnce(runtime::Handle) -> T + Send + 'static, + // ) -> T { + // self.typst_thread.run(f).await + // } +} + +// pub struct SourceScope { +// source: Source, +// project: Project, +// } + +// impl SourceScope { +// pub fn run(self, f: impl FnOnce(&Source, &Project) -> T) -> T { +// f(&self.source, &self.project) +// } + +// pub fn run2(self, f: impl FnOnce(Source, Project) -> T) -> T { +// f(self.source, self.project) +// } +// } + +// pub struct WorldThread<'a> { +// main: Source, +// main_project: Project, +// typst_thread: &'a TypstThread, +// } + +// impl<'a> WorldThread<'a> { +// pub async fn run( +// self, +// f: impl FnOnce(ProjectWorld) -> T + Send + 'static, +// ) -> T { +// self.typst_thread +// .run_with_world(self.main_project, self.main, f) +// .await +// } +// } + +// pub enum WorldBuilder<'a> { +// MainUri(&'a Url), +// MainAndProject(Source, Project), +// } + +// impl<'a> WorldBuilder<'a> { +// async fn main_project(self, workspace: &Arc>) -> +// FsResult<(Source, Project)> { match self { +// Self::MainUri(uri) => { +// let workspace = Arc::clone(workspace).read_owned().await; +// let full_id = workspace.full_id(uri)?; +// let source = workspace.read_source(uri)?; +// let project = Project::new(full_id.package(), workspace); +// Ok((source, project)) +// } +// Self::MainAndProject(main, project) => Ok((main, project)), +// } +// } +// } + +// impl<'a> From<&'a Url> for WorldBuilder<'a> { +// fn from(uri: &'a Url) -> Self { +// Self::MainUri(uri) +// } +// } + +// impl From<(Source, Project)> for WorldBuilder<'static> { +// fn from((main, project): (Source, Project)) -> Self { +// Self::MainAndProject(main, project) +// } +// } diff --git a/editors/vscode/.eslintignore b/editors/vscode/.eslintignore new file mode 100644 index 00000000..fcd03157 --- /dev/null +++ b/editors/vscode/.eslintignore @@ -0,0 +1,3 @@ +.eslintrc.js +out/ +node_modules/ \ No newline at end of file diff --git a/editors/vscode/.eslintrc.js b/editors/vscode/.eslintrc.js new file mode 100644 index 00000000..3d21cff9 --- /dev/null +++ b/editors/vscode/.eslintrc.js @@ -0,0 +1,38 @@ +module.exports = { + env: { + es2021: true, + node: true, + }, + extends: [ + "plugin:@typescript-eslint/recommended", + "plugin:@typescript-eslint/recommended-requiring-type-checking", + "plugin:@typescript-eslint/strict", + "prettier", + ], + overrides: [], + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + project: "tsconfig.json", + }, + rules: { + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], + "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], + "class-methods-use-this": "off", + "@typescript-eslint/explicit-function-return-type": "error", + "@typescript-eslint/no-explicit-any": "error", + "init-declarations": "off", + "@typescript-eslint/init-declarations": "error", + "no-undef-init": "off", + "@typescript-eslint/strict-boolean-expressions": [ + "error", + { + allowString: false, + allowNumber: false, + allowNullableObject: false, + allowNullableEnum: false, + }, + ], + }, +}; diff --git a/editors/vscode/.gitignore b/editors/vscode/.gitignore new file mode 100644 index 00000000..90d026a1 --- /dev/null +++ b/editors/vscode/.gitignore @@ -0,0 +1 @@ +tinymist-*.vsix diff --git a/editors/vscode/.prettierignore b/editors/vscode/.prettierignore new file mode 100644 index 00000000..33b3d738 --- /dev/null +++ b/editors/vscode/.prettierignore @@ -0,0 +1,11 @@ +.DS_Store +icons/ +node_modules/ +out/ +.env +.env.* + +# Ignore files for PNPM, NPM and YARN +pnpm-lock.yaml +package-lock.json +yarn.lock diff --git a/editors/vscode/.prettierrc.js b/editors/vscode/.prettierrc.js new file mode 100644 index 00000000..003b517f --- /dev/null +++ b/editors/vscode/.prettierrc.js @@ -0,0 +1,13 @@ +module.exports = { + // same options as rust-analyzer, otherwise defaults from prettier + printWidth: 100, + tabWidth: 4, + useTabs: false, + semi: true, + singleQuote: false, + quoteProps: "as-needed", + trailingComma: "es5", + bracketSpacing: true, + arrowParens: "always", + singleAttributePerLine: false, +}; diff --git a/editors/vscode/.vscodeignore b/editors/vscode/.vscodeignore new file mode 100644 index 00000000..344189f7 --- /dev/null +++ b/editors/vscode/.vscodeignore @@ -0,0 +1,11 @@ +** +!language-configuration.json +!typst.tmLanguage.json +!out/extension.js +!out/tinymist +!out/tinymist.exe +!package.json +!package-lock.json +!icons/** +!README.md +!LICENSE.md diff --git a/editors/vscode/LICENSE b/editors/vscode/LICENSE new file mode 100644 index 00000000..31931256 --- /dev/null +++ b/editors/vscode/LICENSE @@ -0,0 +1,201 @@ + 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 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. diff --git a/editors/vscode/README.md b/editors/vscode/README.md new file mode 100644 index 00000000..15a72e77 --- /dev/null +++ b/editors/vscode/README.md @@ -0,0 +1,29 @@ +# tinymist Typst LSP VS Code Extension + +A VS Code extension for Typst. + +## Features + +- Syntax highlighting, error reporting, code completion, and function signature + help +- Compiles to PDF on save (configurable to as-you-type, or can be disabled) + +## Usage Tips + +- This extension compiles to PDF, but it doesn't have a PDF viewer yet. To view + the output as you work, install a PDF viewer extension, such as + `vscode-pdf`. +- To configure when PDFs are compiled: + 1. Open settings + - File -> Preferences -> Settings (Linux, Windows) + - Code -> Preferences -> Settings (Mac) + 2. Search for "Typst Export PDF" + 3. Change the Export PDF setting + - `onSave` makes a PDF after saving the Typst file + - `onType` makes PDF files live, as you type + - `never` disables PDF compilation + +## Technical + +The extension uses [Typst LSP](https://github.com/_/tinymist) on the +backend. diff --git a/editors/vscode/icons/typst-small.png b/editors/vscode/icons/typst-small.png new file mode 100644 index 00000000..24edfbab Binary files /dev/null and b/editors/vscode/icons/typst-small.png differ diff --git a/editors/vscode/language-configuration.json b/editors/vscode/language-configuration.json new file mode 100644 index 00000000..c3db14bb --- /dev/null +++ b/editors/vscode/language-configuration.json @@ -0,0 +1,46 @@ +{ + "comments": { + "lineComment": "//", + "blockComment": ["/*", "*/"] + }, + "brackets": [ + ["[", "]"], + ["{", "}"], + ["(", ")"] + ], + "autoClosingPairs": [ + { + "open": "[", + "close": "]" + }, + { + "open": "{", + "close": "}" + }, + { + "open": "(", + "close": ")" + }, + { + "open": "\"", + "close": "\"", + "notIn": ["string"] + }, + { + "open": "$", + "close": "$", + "notIn": ["string"] + } + ], + "autoCloseBefore": "$ \n\t", + "surroundingPairs": [ + ["[", "]"], + ["{", "}"], + ["(", ")"], + ["\"", "\""], + ["*", "*"], + ["_", "_"], + ["`", "`"], + ["$", "$"] + ] +} diff --git a/editors/vscode/package.json b/editors/vscode/package.json new file mode 100644 index 00000000..1713b76a --- /dev/null +++ b/editors/vscode/package.json @@ -0,0 +1,349 @@ +{ + "name": "tinymist", + "description": "A language server for Typst", + "repository": { + "type": "git", + "url": "https://github.com/Myriad-Dreamin/tinymist" + }, + "displayName": "Tinymist Typst LSP", + "author": "Myriad-Dreamin", + "contributors": [ + "Myriad-Dreamin", + "Nathan Varner" + ], + "publisher": "Myriad-Dreamin", + "license": "Apache-2.0 OR MIT", + "version": "0.12.0", + "engines": { + "vscode": "^1.71.0" + }, + "main": "./out/extension.js", + "contributes": { + "configuration": { + "type": "object", + "title": "Typst LSP", + "properties": { + "tinymist.exportPdf": { + "title": "Export PDF", + "description": "The extension can export PDFs of your Typst files. This setting controls whether this feature is enabled and how often it runs.", + "type": "string", + "default": "onSave", + "enum": [ + "never", + "onSave", + "onType" + ], + "enumDescriptions": [ + "Never export PDFs, you will manually run typst.", + "Export PDFs when you save a file.", + "Export PDFs as you type in a file." + ] + }, + "tinymist.rootPath": { + "title": "Root path", + "description": "Configure the root for absolute paths in typst", + "type": [ + "string", + "null" + ], + "default": null + }, + "tinymist.semanticTokens": { + "title": "Semantic tokens mode", + "description": "Enable or disable semantic tokens (LSP syntax highlighting)", + "type": "string", + "default": "enable", + "enum": [ + "enable", + "disable" + ], + "enumDescriptions": [ + "Use semantic tokens for syntax highlighting", + "Do not use semantic tokens for syntax highlighting" + ] + }, + "tinymist.serverPath": { + "title": "Path to server executable", + "description": "The extension can use a local tinymist executable instead of the one bundled with the extension. This setting controls the path to the executable.", + "type": [ + "string", + "null" + ], + "default": null + }, + "tinymist.trace.server": { + "scope": "window", + "type": "string", + "enum": [ + "off", + "messages", + "verbose" + ], + "default": "off", + "description": "Traces the communication between VS Code and the language server." + }, + "tinymist.experimentalFormatterMode": { + "title": "Enable Experimental Formatter", + "description": "The extension can format Typst files using typstfmt (experimental).", + "type": "string", + "default": "off", + "enum": [ + "off", + "on" + ], + "enumDescriptions": [ + "Formatter is not activated.", + "Experimental formatter is activated." + ] + } + } + }, + "configurationDefaults": { + "[typst]": { + "editor.wordWrap": "on", + "editor.semanticHighlighting.enabled": true, + "editor.tabSize": 2 + } + }, + "languages": [ + { + "id": "typst", + "configuration": "./language-configuration.json", + "extensions": [ + ".typ" + ], + "aliases": [ + "Typst" + ], + "icon": { + "light": "./icons/typst-small.png", + "dark": "./icons/typst-small.png" + } + } + ], + "semanticTokenTypes": [ + { + "id": "bool", + "description": "A boolean literal" + }, + { + "id": "punct", + "description": "Punctuation in code" + }, + { + "id": "escape", + "description": "Escape sequence" + }, + { + "id": "link", + "description": "Hyperlink" + }, + { + "id": "raw", + "description": "Raw text" + }, + { + "id": "label", + "description": "Label" + }, + { + "id": "ref", + "description": "Reference to a label" + }, + { + "id": "heading", + "description": "Heading" + }, + { + "id": "marker", + "description": "List, enum, or term list marker" + }, + { + "id": "term", + "description": "Term in a term list" + }, + { + "id": "delim", + "description": "Delimiter of a different type of markup" + }, + { + "id": "pol", + "description": "Interpolated variable" + }, + { + "id": "error", + "description": "Syntax error" + }, + { + "id": "text", + "description": "Text" + } + ], + "semanticTokenModifiers": [ + { + "id": "math", + "description": "Math mode markup" + }, + { + "id": "strong", + "description": "Strong (usually bolded) text" + }, + { + "id": "emph", + "description": "Emphasized (usually italicized) text" + } + ], + "semanticTokenScopes": [ + { + "language": "typst", + "scopes": { + "*.strong.emph": [ + "markup.bold.typst markup.italic.typst" + ], + "*.strong": [ + "markup.bold.typst" + ], + "*.emph": [ + "markup.italic.typst" + ], + "*.math": [ + "markup.math.typst" + ], + "bool": [ + "constant.language.boolean.typst" + ], + "punct": [ + "punctuation.typst", + "punctuation.definition.typst" + ], + "escape": [ + "constant.character.escape.typst", + "keyword.operator.typst", + "punctuation.definition.typst" + ], + "link": [ + "markup.underline.link.typst" + ], + "raw": [ + "markup.inline.raw.typst", + "markup.raw.inline.typst" + ], + "delim.math": [ + "punctuation.definition.math.typst", + "punctuation.definition.string.end.math.typst", + "string.quoted.other.typst" + ], + "operator.math": [ + "keyword.operator.math.typst" + ], + "heading": [ + "markup.heading.typst" + ], + "marker": [ + "markup.list.typst punctuation.definition.list.begin.typst", + "markup.list.typst", + "punctuation.definition.list.begin.typst" + ], + "term": [ + "markup.list.term.typst", + "markup.bold.term.typst" + ], + "label": [ + "string.other.link.title.typst", + "entity.name.label.typst", + "meta.link.inline.typst", + "markup.underline.link.typst" + ], + "ref": [ + "string.other.link.typst", + "markup.other.reference.typst", + "entity.name.label.typst", + "meta.link.inline.typst", + "markup.underline.link.typst" + ], + "pol": [ + "meta.interpolation.typst", + "variable.typst" + ], + "error": [ + "invalid.typst" + ] + } + } + ], + "commands": [ + { + "command": "tinymist.exportCurrentPdf", + "title": "Export the currently open file as PDF", + "category": "Typst" + }, + { + "command": "tinymist.showPdf", + "title": "Show the compiled PDF of the currently opened typst file", + "category": "Typst", + "icon": "$(open-preview)" + }, + { + "command": "tinymist.clearCache", + "title": "Clear all cached resources", + "category": "Typst" + } + ], + "menus": { + "commandPalette": [ + { + "command": "tinymist.exportCurrentPdf", + "when": "editorLangId == typst" + }, + { + "command": "tinymist.clearCache", + "when": "editorLangId == typst" + } + ], + "editor/title": [ + { + "command": "tinymist.showPdf", + "group": "navigation", + "when": "editorLangId == typst" + } + ] + } + }, + "activationEvents": [ + "onLanguage:typst", + "onCommand:tinymist.exportCurrentPdf", + "onCommand:tinymist.clearCache" + ], + "scripts": { + "build-base": "esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node --target=node16", + "vscode:prepublish": "npm run build-base -- --minify", + "package": "vsce package", + "compile": "npm run build-base -- --sourcemap", + "watch": "npm run build-base -- --sourcemap --watch", + "check": "tsc --noEmit", + "lint": "eslint ./src --ext .ts", + "lint-fix": "eslint ./src --ext .ts --fix", + "format-check": "prettier --check .", + "format": "prettier --write .", + "test": "" + }, + "dependencies": { + "vscode-languageclient": "^9.0.1" + }, + "devDependencies": { + "@types/node": "^20.8.10", + "@types/vscode": "~1.71.0", + "@typescript-eslint/eslint-plugin": "^6.9.1", + "@typescript-eslint/parser": "^6.9.1", + "@vscode/vsce": "^2.22.0", + "esbuild": "^0.19.5", + "eslint": "^8.52.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-n": "^16.2.0", + "eslint-plugin-promise": "^6.1.1", + "ovsx": "^0.8.3", + "prettier": "^3.0.3", + "typescript": "^5.2.2" + } +} diff --git a/editors/vscode/src/extension.ts b/editors/vscode/src/extension.ts new file mode 100644 index 00000000..fb9932db --- /dev/null +++ b/editors/vscode/src/extension.ts @@ -0,0 +1,174 @@ +import { + type ExtensionContext, + workspace, + window, + commands, + ViewColumn, + Uri, + WorkspaceConfiguration, +} from "vscode"; +import * as path from "path"; +import * as child_process from "child_process"; + +import { + LanguageClient, + type LanguageClientOptions, + type ServerOptions, +} from "vscode-languageclient/node"; + +let client: LanguageClient | undefined = undefined; + +export function activate(context: ExtensionContext): Promise { + return startClient(context).catch((e) => { + void window.showErrorMessage(`Failed to activate tinymist: ${e}`); + throw e; + }); +} + +async function startClient(context: ExtensionContext): Promise { + const config = workspace.getConfiguration("tinymist"); + const serverCommand = getServer(config); + const run = { + command: serverCommand, + options: { env: Object.assign({}, process.env, { RUST_BACKTRACE: "1" }) }, + }; + const serverOptions: ServerOptions = { + run, + debug: run, + }; + + const clientOptions: LanguageClientOptions = { + documentSelector: [{ scheme: "file", language: "typst" }], + initializationOptions: config, + }; + + client = new LanguageClient( + "tinymist", + "Tinymist Typst Language Server", + serverOptions, + clientOptions + ); + + context.subscriptions.push( + commands.registerCommand("tinymist.exportCurrentPdf", commandExportCurrentPdf) + ); + context.subscriptions.push(commands.registerCommand("tinymist.showPdf", commandShowPdf)); + context.subscriptions.push(commands.registerCommand("tinymist.clearCache", commandClearCache)); + + return client.start(); +} + +export function deactivate(): Promise | undefined { + return client?.stop(); +} + +function getServer(conf: WorkspaceConfiguration): string { + const pathInConfig = conf.get("serverPath"); + if (pathInConfig !== undefined && pathInConfig !== null && pathInConfig !== "") { + const validation = validateServer(pathInConfig); + if (!validation.valid) { + throw new Error( + `\`tinymist.serverPath\` (${pathInConfig}) does not point to a valid tinymist binary:\n${validation.message}` + ); + } + return pathInConfig; + } + const windows = process.platform === "win32"; + const suffix = windows ? ".exe" : ""; + const binaryName = "tinymist" + suffix; + + const bundledPath = path.resolve(__dirname, binaryName); + + const bundledValidation = validateServer(bundledPath); + if (bundledValidation.valid) { + return bundledPath; + } + + const binaryValidation = validateServer(binaryName); + if (binaryValidation.valid) { + return binaryName; + } + + throw new Error( + `Could not find a valid tinymist binary.\nBundled: ${bundledValidation.message}\nIn PATH: ${binaryValidation.message}` + ); +} + +function validateServer(path: string): { valid: true } | { valid: false; message: string } { + try { + const result = child_process.spawnSync(path); + if (result.status === 0) { + return { valid: true }; + } else { + const statusMessage = result.status !== null ? [`return status: ${result.status}`] : []; + const errorMessage = + result.error?.message !== undefined ? [`error: ${result.error.message}`] : []; + const messages = [statusMessage, errorMessage]; + const messageSuffix = + messages.length !== 0 ? `:\n\t${messages.flat().join("\n\t")}` : ""; + const message = `Failed to launch '${path}'${messageSuffix}`; + return { valid: false, message }; + } + } catch (e) { + if (e instanceof Error) { + return { valid: false, message: `Failed to launch '${path}': ${e.message}` }; + } else { + return { valid: false, message: `Failed to launch '${path}': ${JSON.stringify(e)}` }; + } + } +} + +async function commandExportCurrentPdf(): Promise { + const activeEditor = window.activeTextEditor; + if (activeEditor === undefined) { + return; + } + + const uri = activeEditor.document.uri.toString(); + + await client?.sendRequest("workspace/executeCommand", { + command: "tinymist.doPdfExport", + arguments: [uri], + }); +} + +/** + * Implements the functionality for the 'Show PDF' button shown in the editor title + * if a `.typ` file is opened. + */ +async function commandShowPdf(): Promise { + const activeEditor = window.activeTextEditor; + if (activeEditor === undefined) { + return; + } + + const uri = activeEditor.document.uri; + // change the file extension to `.pdf` as we want to open the pdf file + // and not the currently opened `.typ` file. + const n = uri.toString().lastIndexOf("."); + const pdf_uri = Uri.parse(uri.toString().slice(0, n) + ".pdf"); + + try { + await workspace.fs.stat(pdf_uri); + } catch { + // only create pdf if it does not exist yet + await commandExportCurrentPdf(); + } finally { + // here we can be sure that the pdf exists + await commands.executeCommand("vscode.open", pdf_uri, ViewColumn.Beside); + } +} + +async function commandClearCache(): Promise { + const activeEditor = window.activeTextEditor; + if (activeEditor === undefined) { + return; + } + + const uri = activeEditor.document.uri.toString(); + + await client?.sendRequest("workspace/executeCommand", { + command: "tinymist.doClearCache", + arguments: [uri], + }); +} diff --git a/editors/vscode/tsconfig.json b/editors/vscode/tsconfig.json new file mode 100644 index 00000000..095b5cf7 --- /dev/null +++ b/editors/vscode/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2020", + "lib": ["es2020"], + "outDir": "out", + "rootDir": "src", + "sourceMap": true, + "strict": true + }, + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/editors/vscode/typst.tmLanguage.json b/editors/vscode/typst.tmLanguage.json new file mode 100644 index 00000000..000a24b1 --- /dev/null +++ b/editors/vscode/typst.tmLanguage.json @@ -0,0 +1,576 @@ +{ + "name": "typst", + "patterns": [ + { + "include": "#markup" + } + ], + "repository": { + "comments": { + "patterns": [ + { + "name": "comment.block.typst", + "begin": "/\\*", + "end": "\\*/", + "captures": { + "0": { + "name": "punctuation.definition.comment.typst" + } + }, + "patterns": [ + { + "include": "#comments" + } + ] + }, + { + "name": "comment.line.double-slash.typst", + "begin": "(?", + "captures": { + "1": { + "name": "punctuation.definition.label.typst" + } + } + }, + { + "name": "entity.other.reference.typst", + "match": "(@)[[:alpha:]_][[:alnum:]_-]*", + "captures": { + "1": { + "name": "punctuation.definition.reference.typst" + } + } + }, + { + "begin": "(#)(let|set|show)\\b", + "end": "\n|(;)|(?=])", + "beginCaptures": { + "0": { + "name": "keyword.other.typst" + }, + "1": { + "name": "punctuation.definition.keyword.typst" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.terminator.statement.typst" + } + }, + "patterns": [ + { + "include": "#code" + } + ] + }, + { + "name": "keyword.other.typst", + "match": "(#)(as|in)\\b", + "captures": { + "1": { + "name": "punctuation.definition.keyword.typst" + } + } + }, + { + "begin": "((#)if|(?<=(}|])\\s*)else)\\b", + "end": "\n|(?=])|(?<=}|])", + "beginCaptures": { + "0": { + "name": "keyword.control.conditional.typst" + }, + "2": { + "name": "punctuation.definition.keyword.typst" + } + }, + "patterns": [ + { + "include": "#code" + } + ] + }, + { + "begin": "(#)(for|while)\\b", + "end": "\n|(?=])|(?<=}|])", + "beginCaptures": { + "0": { + "name": "keyword.control.loop.typst" + }, + "1": { + "name": "punctuation.definition.keyword.typst" + } + }, + "patterns": [ + { + "include": "#code" + } + ] + }, + { + "name": "keyword.control.loop.typst", + "match": "(#)(break|continue)\\b", + "captures": { + "1": { + "name": "punctuation.definition.keyword.typst" + } + } + }, + { + "begin": "(#)(import|include|export)\\b", + "end": "\n|(;)|(?=])", + "beginCaptures": { + "0": { + "name": "keyword.control.import.typst" + }, + "1": { + "name": "punctuation.definition.keyword.typst" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.terminator.statement.typst" + } + }, + "patterns": [ + { + "include": "#code" + } + ] + }, + { + "name": "keyword.control.flow.typst", + "match": "(#)(return)\\b", + "captures": { + "1": { + "name": "punctuation.definition.keyword.typst" + } + } + }, + { + "comment": "Function name", + "name": "entity.name.function.typst", + "match": "((#)[[:alpha:]_][[:alnum:]_-]*!?)(?=\\[|\\()", + "captures": { + "2": { + "name": "punctuation.definition.function.typst" + } + } + }, + { + "comment": "Function arguments", + "begin": "(?<=#[[:alpha:]_][[:alnum:]_-]*!?)\\(", + "end": "\\)", + "captures": { + "0": { + "name": "punctuation.definition.group.typst" + } + }, + "patterns": [ + { + "include": "#arguments" + } + ] + }, + { + "name": "entity.other.interpolated.typst", + "match": "(#)[[:alpha:]_][.[:alnum:]_-]*", + "captures": { + "1": { + "name": "punctuation.definition.variable.typst" + } + } + }, + { + "name": "meta.block.content.typst", + "begin": "#", + "end": "\\s", + "patterns": [ + { + "include": "#code" + } + ] + } + ] + }, + "code": { + "patterns": [ + { + "include": "#common" + }, + { + "name": "meta.block.code.typst", + "begin": "{", + "end": "}", + "captures": { + "0": { + "name": "punctuation.definition.block.code.typst" + } + }, + "patterns": [ + { + "include": "#code" + } + ] + }, + { + "name": "meta.block.content.typst", + "begin": "\\[", + "end": "\\]", + "captures": { + "0": { + "name": "punctuation.definition.block.content.typst" + } + }, + "patterns": [ + { + "include": "#markup" + } + ] + }, + { + "name": "comment.line.double-slash.typst", + "begin": "//", + "end": "\n", + "beginCaptures": { + "0": { + "name": "punctuation.definition.comment.typst" + } + } + }, + { + "name": "punctuation.separator.colon.typst", + "match": ":" + }, + { + "name": "punctuation.separator.comma.typst", + "match": "," + }, + { + "name": "keyword.operator.typst", + "match": "=>|\\.\\." + }, + { + "name": "keyword.operator.relational.typst", + "match": "==|!=|<=|<|>=|>" + }, + { + "name": "keyword.operator.assignment.typst", + "match": "\\+=|-=|\\*=|/=|=" + }, + { + "name": "keyword.operator.arithmetic.typst", + "match": "\\+|\\*|/|(?