feat: add command to profile the entire server (#1438)

* feat: declare and bind tinymist.profileServer command

* feat: editor bridge with the frontend

* feat: start and stop server profiling

* feat: add profile-server prototype (#1440)

* Add profile-server prototype

* fix: use branch

---------

Co-authored-by: Myriad-Dreamin <camiyoru@gmail.com>

* feat: make it good

* build: update cargo.lock

* dev: ls profile impl and hook

* test: update snapshot

---------

Co-authored-by: Derived Cat <hooyuser@outlook.com>
This commit is contained in:
Myriad-Dreamin 2025-05-09 15:29:24 +08:00 committed by GitHub
parent 890ecd93a5
commit d6d3766b6f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 858 additions and 298 deletions

301
Cargo.lock generated
View file

@ -23,7 +23,7 @@ version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
dependencies = [
"getrandom 0.2.15",
"getrandom 0.2.16",
"once_cell",
"version_check",
]
@ -185,7 +185,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -208,9 +208,9 @@ checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973"
[[package]]
name = "backtrace"
version = "0.3.74"
version = "0.3.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
dependencies = [
"addr2line",
"cfg-if",
@ -346,9 +346,9 @@ dependencies = [
[[package]]
name = "bytemuck"
version = "1.22.0"
version = "1.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540"
checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c"
[[package]]
name = "byteorder"
@ -408,9 +408,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.2.19"
version = "1.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362"
checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0"
dependencies = [
"shlex",
]
@ -447,9 +447,9 @@ checksum = "7588475145507237ded760e52bf2f1085495245502033756d28ea72ade0e498b"
[[package]]
name = "chrono"
version = "0.4.40"
version = "0.4.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
dependencies = [
"android-tzdata",
"iana-time-zone",
@ -499,9 +499,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.36"
version = "4.5.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04"
checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071"
dependencies = [
"clap_builder",
"clap_derive",
@ -509,9 +509,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.36"
version = "4.5.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "132dbda40fb6753878316a489d5a1242a8ef2f0d9e47ba01c951ea8aa7d013a5"
checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2"
dependencies = [
"anstream",
"anstyle",
@ -524,9 +524,9 @@ dependencies = [
[[package]]
name = "clap_complete"
version = "4.5.47"
version = "4.5.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06f5378ea264ad4f82bbc826628b5aad714a75abf6ece087e923010eb937fb6"
checksum = "c91d3baa3bcd889d60e6ef28874126a0b384fd225ab83aa6d8a801c519194ce1"
dependencies = [
"clap",
]
@ -560,7 +560,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -596,7 +596,7 @@ checksum = "01be201bdfedabd81a11363ab87c1bc92baedf90039437bede5eee0ea9ece8d5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -653,7 +653,7 @@ checksum = "c8936e42f9b4f5bdfaf23700609ac1f11cb03ad4c1ec128a4ee4fd0903e228db"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -881,7 +881,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -892,7 +892,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
dependencies = [
"darling_core",
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -1006,7 +1006,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -1031,7 +1031,7 @@ checksum = "9556bc800956545d6420a640173e5ba7dfa82f38d3ea5a167eb555bc69ac3323"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -1055,9 +1055,9 @@ checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
[[package]]
name = "ecow"
version = "0.2.4"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef5eeffa816451f3a6a4cce9cd796e3e5ba6018638d3ce5cc7f87b73bababf60"
checksum = "b92b481eb5d59fd8e80e92ff11d057d1ca8d144b2cd8c66cc8d5bd177a3c0dc5"
dependencies = [
"serde",
]
@ -1121,7 +1121,7 @@ checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -1337,7 +1337,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -1391,9 +1391,9 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.2.15"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"cfg-if",
"js-sys",
@ -1447,9 +1447,9 @@ dependencies = [
[[package]]
name = "h2"
version = "0.4.9"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633"
checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5"
dependencies = [
"atomic-waker",
"bytes",
@ -1494,9 +1494,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.15.2"
version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
dependencies = [
"foldhash",
]
@ -1539,9 +1539,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.5.0"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e"
checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08"
[[package]]
name = "hex"
@ -1631,7 +1631,7 @@ dependencies = [
"tokio",
"tokio-rustls",
"tower-service",
"webpki-roots",
"webpki-roots 0.26.11",
]
[[package]]
@ -1845,7 +1845,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -1954,7 +1954,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
dependencies = [
"equivalent",
"hashbrown 0.15.2",
"hashbrown 0.15.3",
"serde",
]
@ -1980,15 +1980,13 @@ dependencies = [
[[package]]
name = "insta"
version = "1.42.2"
version = "1.43.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50259abbaa67d11d2bcafc7ba1d094ed7a0c70e3ce893f0d0997f73558cb3084"
checksum = "154934ea70c58054b556dd430b99a98c2a7ff5309ac9891597e339b5c28f4371"
dependencies = [
"console",
"globset",
"linked-hash-map",
"once_cell",
"pin-project",
"similar",
"walkdir",
]
@ -2070,9 +2068,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jiff"
version = "0.2.8"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ad87c89110f55e4cd4dc2893a9790820206729eaf221555f742d540b0724a0"
checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806"
dependencies = [
"jiff-static",
"log",
@ -2083,13 +2081,13 @@ dependencies = [
[[package]]
name = "jiff-static"
version = "0.2.8"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d076d5b64a7e2fe6f0743f02c43ca4a6725c0f904203bfe276a5b3e793103605"
checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -2113,9 +2111,9 @@ dependencies = [
[[package]]
name = "kqueue"
version = "1.0.8"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c"
checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a"
dependencies = [
"kqueue-sys",
"libc",
@ -2133,9 +2131,9 @@ dependencies = [
[[package]]
name = "kurbo"
version = "0.11.1"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f"
checksum = "1077d333efea6170d9ccb96d3c3026f300ca0773da4938cc4c811daa6df68b0c"
dependencies = [
"arrayvec 0.7.6",
"smallvec",
@ -2161,9 +2159,9 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "libm"
version = "0.2.11"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]]
name = "libredox"
@ -2279,9 +2277,9 @@ dependencies = [
[[package]]
name = "mintex"
version = "0.1.3"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bec4598fddb13cc7b528819e697852653252b760f1228b7642679bf2ff2cd07"
checksum = "c505b3e17ed6b70a7ed2e67fbb2c560ee327353556120d6e72f5232b6880d536"
[[package]]
name = "mio"
@ -2451,7 +2449,7 @@ dependencies = [
"by_address",
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -2543,7 +2541,7 @@ dependencies = [
"phf_shared",
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -2561,26 +2559,6 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
[[package]]
name = "pin-project"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
@ -2695,7 +2673,7 @@ version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy 0.8.24",
"zerocopy 0.8.25",
]
[[package]]
@ -2734,9 +2712,9 @@ dependencies = [
[[package]]
name = "psm"
version = "0.1.25"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f58e5423e24c18cc840e1c98370b3993c6649cd1678b4d24318bcf0a083cbe88"
checksum = "6e944464ec8536cd1beb0bbfd96987eb5e3b72f2ecdafdc5c769a37f1fa2ae1f"
dependencies = [
"cc",
]
@ -2814,13 +2792,13 @@ dependencies = [
[[package]]
name = "quinn-proto"
version = "0.11.10"
version = "0.11.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc"
checksum = "bcbafbbdbb0f638fe3f35f3c56739f77a8a1d070cb25603226c83339b391472b"
dependencies = [
"bytes",
"getrandom 0.3.2",
"rand 0.9.0",
"rand 0.9.1",
"ring",
"rustc-hash 2.1.1",
"rustls",
@ -2834,9 +2812,9 @@ dependencies = [
[[package]]
name = "quinn-udp"
version = "0.5.11"
version = "0.5.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "541d0f57c6ec747a90738a52741d3221f7960e8ac2f0ff4b1a63680e033b4ab5"
checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842"
dependencies = [
"cfg_aliases",
"libc",
@ -2880,13 +2858,12 @@ dependencies = [
[[package]]
name = "rand"
version = "0.9.0"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.3",
"zerocopy 0.8.24",
]
[[package]]
@ -2915,7 +2892,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.15",
"getrandom 0.2.16",
]
[[package]]
@ -2949,9 +2926,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.5.11"
version = "0.5.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3"
checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
dependencies = [
"bitflags 2.9.0",
]
@ -2962,7 +2939,7 @@ version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
dependencies = [
"getrandom 0.2.15",
"getrandom 0.2.16",
"libredox",
"thiserror 1.0.69",
]
@ -2973,7 +2950,7 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
dependencies = [
"getrandom 0.2.15",
"getrandom 0.2.16",
"libredox",
"thiserror 2.0.12",
]
@ -3164,7 +3141,7 @@ dependencies = [
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"webpki-roots",
"webpki-roots 0.26.11",
"windows-registry",
]
@ -3202,7 +3179,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.15",
"getrandom 0.2.16",
"libc",
"untrusted",
"windows-sys 0.52.0",
@ -3321,9 +3298,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "1.0.5"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
dependencies = [
"bitflags 2.9.0",
"errno",
@ -3334,9 +3311,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.26"
version = "0.23.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0"
checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321"
dependencies = [
"once_cell",
"ring",
@ -3366,9 +3343,9 @@ dependencies = [
[[package]]
name = "rustls-webpki"
version = "0.103.1"
version = "0.103.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03"
checksum = "7149975849f1abb3832b246010ef62ccc80d3a76169517ada7188252b9cfb437"
dependencies = [
"ring",
"rustls-pki-types",
@ -3463,7 +3440,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -3486,7 +3463,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -3537,7 +3514,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -3566,9 +3543,9 @@ dependencies = [
[[package]]
name = "sha2"
version = "0.10.8"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures",
@ -3583,9 +3560,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
version = "1.4.2"
version = "1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410"
dependencies = [
"libc",
]
@ -3671,9 +3648,9 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "stacker"
version = "0.1.20"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "601f9201feb9b09c00266478bf459952b9ef9a6b94edb2f21eba14ab681a60a9"
checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b"
dependencies = [
"cc",
"cfg-if",
@ -3697,7 +3674,7 @@ version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a3275464d7a9f2d4cac57c89c2ef96a8524dba2864c8d6f82e3980baf136f9b"
dependencies = [
"hashbrown 0.15.2",
"hashbrown 0.15.3",
"serde",
]
@ -3726,7 +3703,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -3787,9 +3764,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.100"
version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [
"proc-macro2",
"quote",
@ -3825,13 +3802,13 @@ dependencies = [
[[package]]
name = "synstructure"
version = "0.13.1"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -3969,7 +3946,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -3980,7 +3957,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -4149,7 +4126,9 @@ dependencies = [
"toml",
"triomphe",
"typst",
"typst-macros",
"typst-shim",
"typst-timing",
"unscanny",
]
@ -4225,7 +4204,7 @@ name = "tinymist-derive"
version = "0.13.12"
dependencies = [
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -4357,7 +4336,9 @@ dependencies = [
"typlite",
"typst",
"typst-assets",
"typst-macros",
"typst-shim",
"typst-timing",
"unscanny",
"walkdir",
"yaml-rust2",
@ -4553,9 +4534,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.44.2"
version = "1.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165"
dependencies = [
"backtrace",
"bytes",
@ -4577,7 +4558,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -4604,9 +4585,9 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.7.14"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034"
checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
dependencies = [
"bytes",
"futures-core",
@ -4618,9 +4599,9 @@ dependencies = [
[[package]]
name = "toml"
version = "0.8.20"
version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae"
dependencies = [
"serde",
"serde_spanned",
@ -4630,26 +4611,33 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "0.6.8"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.24"
version = "0.22.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e"
dependencies = [
"indexmap 2.9.0",
"serde",
"serde_spanned",
"toml_datetime",
"toml_write",
"winnow",
]
[[package]]
name = "toml_write"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076"
[[package]]
name = "tower"
version = "0.5.2"
@ -4696,7 +4684,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -4966,7 +4954,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -5016,6 +5004,8 @@ dependencies = [
"tokio",
"typst",
"typst-assets",
"typst-macros",
"typst-timing",
]
[[package]]
@ -5414,7 +5404,7 @@ dependencies = [
"log",
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
"wasm-bindgen-shared",
]
@ -5449,7 +5439,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -5539,9 +5529,18 @@ dependencies = [
[[package]]
name = "webpki-roots"
version = "0.26.8"
version = "0.26.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9"
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
dependencies = [
"webpki-roots 1.0.0",
]
[[package]]
name = "webpki-roots"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb"
dependencies = [
"rustls-pki-types",
]
@ -5604,7 +5603,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -5615,7 +5614,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -5876,9 +5875,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "winnow"
version = "0.7.6"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10"
checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec"
dependencies = [
"memchr",
]
@ -5987,7 +5986,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
"synstructure",
]
@ -6002,11 +6001,11 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.24"
version = "0.8.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879"
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
dependencies = [
"zerocopy-derive 0.8.24",
"zerocopy-derive 0.8.25",
]
[[package]]
@ -6017,18 +6016,18 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.24"
version = "0.8.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be"
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]
@ -6048,7 +6047,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
"synstructure",
]
@ -6090,7 +6089,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"syn 2.0.101",
]
[[package]]

View file

@ -137,6 +137,7 @@ reflexo-vec2svg = { version = "=0.6.0-rc1" }
typst = "0.13.1"
typst-html = "0.13.1"
typst-library = "0.13.1"
typst-macros = "0.13.1"
typst-timing = "0.13.1"
typst-svg = "0.13.1"
typst-render = "0.13.1"
@ -266,6 +267,7 @@ extend-exclude = ["/.git", "fixtures"]
#
# A regular build MUST use `tag` or `rev` to specify the version of the patched crate to ensure stability.
typst = { git = "https://github.com/Myriad-Dreamin/typst.git", tag = "tinymist/v0.13.10" }
typst-macros = { git = "https://github.com/Myriad-Dreamin/typst.git", tag = "tinymist/v0.13.10" }
typst-library = { git = "https://github.com/Myriad-Dreamin/typst.git", tag = "tinymist/v0.13.10" }
typst-html = { git = "https://github.com/Myriad-Dreamin/typst.git", tag = "tinymist/v0.13.10" }
typst-timing = { git = "https://github.com/Myriad-Dreamin/typst.git", tag = "tinymist/v0.13.10" }

View file

@ -199,10 +199,18 @@ impl LspClientRoot {
msg_kind: M::get_message_kind(),
sender: Arc::downgrade(&_strong),
req_queue: Arc::new(Mutex::new(ReqQueue::default())),
hook: Arc::new(()),
};
Self { weak, _strong }
}
/// Sets the hook for the language server host.
pub fn with_hook(mut self, hook: Arc<dyn LsHook>) -> Self {
self.weak.hook = hook;
self
}
/// Returns the weak reference to the language server host.
pub fn weak(&self) -> LspClient {
self.weak.clone()
@ -221,6 +229,8 @@ pub struct LspClient {
pub(crate) msg_kind: MessageKind,
pub(crate) sender: Weak<ConnectionTx>,
pub(crate) req_queue: Arc<Mutex<ReqQueue>>,
pub(crate) hook: Arc<dyn LsHook>,
}
impl LspClient {
@ -290,7 +300,7 @@ impl LspClient {
/// Registers an client2server request in the request queue.
pub fn register_request(&self, method: &str, id: &RequestId, received_at: Instant) {
let mut req_queue = self.req_queue.lock();
self.start_request(id, method);
self.hook.start_request(id, method);
req_queue
.incoming
.register(id.clone(), (method.to_owned(), received_at));
@ -327,7 +337,7 @@ impl LspClient {
return;
};
self.stop_request(&id, &method, received_at);
self.hook.stop_request(&id, &method, received_at);
let Some(sender) = self.sender.upgrade() else {
log::warn!("failed to send response ({method}, {id}): connection closed");
@ -380,7 +390,19 @@ impl LspClient {
}
}
impl LspClient {
/// A trait that defines the hook for the language server.
pub trait LsHook: fmt::Debug + Send + Sync {
/// Starts a request.
fn start_request(&self, req_id: &RequestId, method: &str);
/// Stops a request.
fn stop_request(&self, req_id: &RequestId, method: &str, received_at: Instant);
/// Starts a notification.
fn start_notification(&self, method: &str);
/// Stops a notification.
fn stop_notification(&self, method: &str, received_at: Instant, result: LspResult<()>);
}
impl LsHook for () {
fn start_request(&self, req_id: &RequestId, method: &str) {
log::info!("handling {method} - ({req_id})");
}

View file

@ -260,7 +260,7 @@ where
/// Handles an incoming event.
fn on_event(&mut self, received_at: Instant, not: dap::Event) -> anyhow::Result<()> {
self.client.start_notification(&not.event);
self.client.hook.start_notification(&not.event);
let handle = |s,
dap::Event {
seq: _,
@ -273,7 +273,9 @@ where
};
let result = handler(s, body);
self.client.stop_notification(&event, received_at, result);
self.client
.hook
.stop_notification(&event, received_at, result);
Ok(())
};

View file

@ -354,7 +354,7 @@ where
/// Handles an incoming notification.
fn on_notification(&mut self, received_at: Instant, not: Notification) -> anyhow::Result<()> {
self.client.start_notification(&not.method);
self.client.hook.start_notification(&not.method);
let handle = |s, Notification { method, params }: Notification| {
let Some(handler) = self.notifications.get(method.as_str()) else {
log::warn!("unhandled notification: {method}");
@ -362,7 +362,9 @@ where
};
let result = handler(s, params);
self.client.stop_notification(&method, received_at, result);
self.client
.hook
.stop_notification(&method, received_at, result);
Ok(())
};

View file

@ -35,7 +35,9 @@ tinymist-world.workspace = true
toml.workspace = true
triomphe.workspace = true
typst.workspace = true
typst-macros.workspace = true
typst-shim.workspace = true
typst-timing.workspace = true
unscanny.workspace = true
[dev-dependencies]

View file

@ -23,6 +23,7 @@ pub fn analyze_expr(world: &dyn World, node: &LinkedNode) -> EcoVec<(Value, Opti
}
/// Try to determine a set of possible values for an expression.
#[typst_macros::time(span = node.span())]
pub fn analyze_expr_(world: &dyn World, node: &SyntaxNode) -> EcoVec<(Value, Option<Styles>)> {
let Some(expr) = node.cast::<ast::Expr>() else {
return eco_vec![];
@ -51,6 +52,7 @@ pub fn analyze_expr_(world: &dyn World, node: &SyntaxNode) -> EcoVec<(Value, Opt
}
/// Try to load a module from the current source file.
#[typst_macros::time(span = source.span())]
pub fn analyze_import_(world: &dyn World, source: &SyntaxNode) -> (Option<Value>, Option<Value>) {
let source_span = source.span();
let Some((source, _)) = analyze_expr_(world, source).into_iter().next() else {
@ -109,6 +111,7 @@ pub struct DynLabel {
/// - 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.
#[typst_macros::time]
pub fn analyze_labels(document: &TypstDocument) -> (Vec<DynLabel>, usize) {
let mut output = vec![];

View file

@ -51,7 +51,9 @@ tinymist-std.workspace = true
tinymist-l10n.workspace = true
tinymist-lint.workspace = true
typst.workspace = true
typst-macros.workspace = true
typst-shim.workspace = true
typst-timing.workspace = true
unscanny.workspace = true
walkdir.workspace = true
yaml-rust2.workspace = true

View file

@ -38,6 +38,7 @@ pub struct CallInfo {
// todo: cache call
/// Analyzes a function call.
#[typst_macros::time(span = node.span())]
pub fn analyze_call(
ctx: &mut LocalContext,
source: Source,

View file

@ -50,6 +50,7 @@ impl CompletionPair<'_, '_, '_> {
self.def_completions(defines, parens);
}
#[typst_macros::time]
pub fn scope_defs(&mut self) -> Option<Defines> {
let mut defines = Defines {
types: self.worker.ctx.type_check(&self.cursor.source),

View file

@ -81,6 +81,7 @@ impl HasNameRange for Decl {
// todo: field definition
/// Finds the definition of a symbol.
#[typst_macros::time(span = syntax.node().span())]
pub fn definition(
ctx: &Arc<SharedContext>,
source: &Source,

View file

@ -779,6 +779,7 @@ impl SharedContext {
}
/// Get the lint result of a source file.
#[typst_macros::time(span = source.root().span())]
pub(crate) fn lint(self: &Arc<Self>, source: &Source) -> LintInfo {
let ei = self.expr_stage(source);
let ti = self.type_check(source);
@ -1296,6 +1297,7 @@ fn ceil_char_boundary(text: &str, mut cursor: usize) -> usize {
cursor.min(text.len())
}
#[typst_macros::time]
#[comemo::memoize]
fn analyze_bib(
world: Tracked<dyn World + '_>,

View file

@ -8,6 +8,7 @@ use tinymist_world::package::PackageSpec;
use super::prelude::*;
/// Get link expressions from a source.
#[typst_macros::time(span = src.root().span())]
#[comemo::memoize]
pub fn get_link_exprs(src: &Source) -> Arc<LinkInfo> {
let root = LinkedNode::new(src.root());

View file

@ -13,6 +13,7 @@ use crate::ty::BuiltinTy;
/// With given type information, check the type of a literal expression again by
/// touching the possible related nodes.
#[typst_macros::time(span = node.span())]
pub(crate) fn post_type_check(
ctx: Arc<SharedContext>,
ti: &TypeInfo,

View file

@ -26,6 +26,7 @@ use crate::{
pub type SemanticTokens = Arc<Vec<SemanticToken>>;
/// Get the semantic tokens for a source.
#[typst_macros::time(span = source.root().span())]
pub(crate) fn get_semantic_tokens(ctx: &mut LocalContext, source: &Source) -> SemanticTokens {
let mut tokenizer = Tokenizer::new(
source.clone(),

View file

@ -30,6 +30,18 @@ pub enum SignatureTarget {
Convert(Func),
}
impl SignatureTarget {
/// Returns the span of the callee node.
pub fn span(&self) -> Span {
match self {
SignatureTarget::Def(_, def) => def.decl.span(),
SignatureTarget::SyntaxFast(_, span) | SignatureTarget::Syntax(_, span) => *span,
SignatureTarget::Runtime(func) | SignatureTarget::Convert(func) => func.span(),
}
}
}
#[typst_macros::time(span = callee_node.span())]
pub(crate) fn analyze_signature(
ctx: &Arc<SharedContext>,
callee_node: SignatureTarget,
@ -41,6 +53,7 @@ pub(crate) fn analyze_signature(
})
}
#[typst_macros::time(span = callee_node.span())]
fn analyze_type_signature(
ctx: &Arc<SharedContext>,
callee_node: &SignatureTarget,
@ -315,6 +328,7 @@ impl BoundChecker for AliasStackChecker<'_, '_> {
}
}
#[typst_macros::time(span = callee_node.span())]
fn analyze_dyn_signature(
ctx: &Arc<SharedContext>,
callee_node: &SignatureTarget,

View file

@ -29,6 +29,7 @@ pub struct TypeEnv {
}
/// Type checking at the source unit level.
#[typst_macros::time(span = ei.source.root().span())]
pub(crate) fn type_check(
ctx: Arc<SharedContext>,
ei: ExprInfo,
@ -228,14 +229,14 @@ impl TypeChecker<'_> {
&mut self,
sig: &Interned<SigTy>,
args: &Interned<SigTy>,
withs: Option<&Vec<Interned<SigTy>>>,
with: Option<&Vec<Interned<SigTy>>>,
) {
let call_desc = (sig.clone(), args.clone(), withs.cloned());
let call_desc = (sig.clone(), args.clone(), with.cloned());
if !self.call_cache.insert(call_desc) {
return;
}
for (arg_recv, arg_ins) in sig.matches(args, withs) {
for (arg_recv, arg_ins) in sig.matches(args, with) {
self.constrain(arg_ins, arg_recv);
}
}

View file

@ -12,6 +12,7 @@ static EMPTY_DOCSTRING: LazyLock<DocString> = LazyLock::new(DocString::default);
static EMPTY_VAR_DOC: LazyLock<VarDoc> = LazyLock::new(VarDoc::default);
impl TypeChecker<'_> {
#[typst_macros::time(span = expr.span())]
pub(crate) fn check_syntax(&mut self, expr: &Expr) -> Option<Ty> {
Some(match expr {
Expr::Block(exprs) => self.check_block(exprs),

View file

@ -28,6 +28,7 @@ use super::{compute_docstring, def::*, DocCommentMatcher, InterpretMode};
pub type ExprRoute = FxHashMap<TypstFileId, Option<Arc<LazyHash<LexicalScope>>>>;
#[typst_macros::time(span = source.root().span())]
pub(crate) fn expr_of(
ctx: Arc<SharedContext>,
source: Source,

View file

@ -12,6 +12,7 @@ pub struct IndexInfo {
pub(crate) identifiers: FxHashSet<Interned<str>>,
}
#[typst_macros::time(span = src.root().span())]
#[comemo::memoize]
pub fn get_index_info(src: &Source) -> Arc<IndexInfo> {
let root = src.root();

View file

@ -11,6 +11,7 @@ use typst_shim::utils::LazyHash;
use super::{is_mark, CommentGroupMatcher};
#[typst_macros::time(span = source.root().span())]
pub(crate) fn get_lexical_hierarchy(
source: &Source,
scope_kind: LexicalScopeKind,

View file

@ -18,6 +18,7 @@ pub struct ModuleDependency {
/// It will scan all the files in the context, using
/// [`LocalContext::source_files`], and find the dependencies and dependents
/// of each file.
#[typst_macros::time]
pub fn construct_module_dependencies(
ctx: &mut LocalContext,
) -> HashMap<TypstFileId, ModuleDependency> {

View file

@ -318,6 +318,22 @@ impl<T, E: std::fmt::Display> IgnoreLogging<T> for Result<T, E> {
}
}
impl<T> IgnoreLogging<T> for Option<T> {
fn log_error(self, msg: &str) -> Option<T> {
self.or_else(|| {
log::error!("{msg}");
None
})
}
fn log_error_with(self, f: impl FnOnce() -> String) -> Option<T> {
self.or_else(|| {
log::error!("{}", f());
None
})
}
}
/// A trait to add context to a result.
pub trait WithContext<T>: Sized {
/// Add a context to the result.

View file

@ -15,8 +15,7 @@ use rkyv::{Archive, Deserialize as rDeser, Serialize as rSer};
use crate::error::prelude::Result;
pub(crate) type FxBuildHasher = std::hash::BuildHasherDefault<FxHasher>;
pub use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
pub use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet, FxHasher};
// pub type FxIndexSet<K> = indexmap::IndexSet<K, FxHasher>;
// pub type FxIndexMap<K, V> = indexmap::IndexMap<K, V, FxHasher>;
/// A dashmap that uses the FxHasher as the underlying hasher.
@ -34,7 +33,8 @@ pub type FxDashMap<K, V> = dashmap::DashMap<K, V, FxBuildHasher>;
/// > is a probability of 1 in 36,890,000,000,000 of a `StableCrateId`
/// > collision.
///
/// This stores the 16-bytes data in a pair of `u64` instead of single `u128` to avoid heavily affecting `align` of the embedding structs.
/// This stores the 16-bytes data in a pair of `u64` instead of single `u128` to
/// avoid heavily affecting `align` of the embedding structs.
#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "rkyv", derive(Archive, rDeser, rSer))]
#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))]

View file

@ -281,6 +281,33 @@ impl fmt::Debug for Resolving {
}
}
impl fmt::Display for Resolving {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use WorkspaceResolution::*;
let Some(id) = self.id else {
return write!(f, "unresolved-path");
};
let path = match WorkspaceResolver::resolve(id) {
Ok(Workspace(workspace)) => id.vpath().resolve(&workspace.path()),
Ok(UntitledRooted(..)) => Some(id.vpath().as_rootless_path().to_owned()),
Ok(Rootless | Package) | Err(_) => None,
};
if let Some(path) = path {
write!(f, "{}", path.display())
} else {
let pkg = id.package();
match pkg {
Some(pkg) => {
write!(f, "{pkg}{}", id.vpath().as_rooted_path().display())
}
None => write!(f, "{}", id.vpath().as_rooted_path().display()),
}
}
}
}
#[cfg(test)]
mod tests {

View file

@ -523,7 +523,7 @@ impl ServerState {
)
.map_err(internal_error)?;
let task = user_action.trace(TraceParams {
let task = user_action.trace_document(TraceParams {
compiler_program: self_path,
root: root.as_ref().to_owned(),
main,
@ -538,6 +538,51 @@ impl ServerState {
})
}
/// Start to get the trace data of the server.
pub fn start_server_trace(&mut self, _args: Vec<JsonValue>) -> AnySchedulableResponse {
let task_cell = &mut self.server_trace;
if task_cell
.as_ref()
.is_some_and(|task| task.stop_tx.is_closed())
{
*task_cell = None;
}
if task_cell.is_some() {
return Err(internal_error("server trace is already started"));
}
let (task, resp) = self.user_action.trace_server();
*task_cell = Some(task);
log::info!("server trace started");
resp
}
/// Stop getting the trace data of the server.
pub fn stop_server_trace(&mut self, _args: Vec<JsonValue>) -> AnySchedulableResponse {
let task_cell = &mut self.server_trace;
if task_cell
.as_ref()
.is_some_and(|task| task.stop_tx.is_closed())
{
log::info!("server trace is dropped");
*task_cell = None;
}
let Some(task) = task_cell.take() else {
return Err(internal_error("server trace is not started or stopped"));
};
if task.stop_tx.send(()).is_err() {
return Err(internal_error("cannot send stop signal to server trace"));
}
log::info!("server trace stopping");
just_future(async move { task.resp_rx.await.map_err(internal_error)? })
}
/// Get the metrics of the document.
pub fn get_document_metrics(
&mut self,

View file

@ -25,6 +25,7 @@ impl ServerState {
/// Creates a new source file.
pub fn create_source(&mut self, path: ImmutPath, content: String) -> Result<()> {
let _scope = typst_timing::TimingScope::new("create_source");
log::trace!("create source: {path:?}");
self.memory_changes
.insert(path.clone(), Source::detached(content.clone()));
@ -39,6 +40,7 @@ impl ServerState {
/// Removes a source file.
pub fn remove_source(&mut self, path: ImmutPath) -> Result<()> {
let _scope = typst_timing::TimingScope::new("remove_source");
self.memory_changes.remove(&path);
log::trace!("remove source: {path:?}");
@ -55,6 +57,7 @@ impl ServerState {
content: Vec<TextDocumentContentChangeEvent>,
position_encoding: PositionEncoding,
) -> Result<()> {
let _scope = typst_timing::TimingScope::new("edit_source");
let source = self
.memory_changes
.get_mut(&path)

View file

@ -2,21 +2,24 @@
mod args;
use core::fmt;
use std::collections::HashMap;
use std::io;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::LazyLock;
use std::sync::{Arc, LazyLock};
use clap::Parser;
use clap_builder::CommandFactory;
use clap_complete::generate;
use futures::future::MaybeDone;
use parking_lot::Mutex;
use reflexo::ImmutPath;
use reflexo_typst::package::PackageSpec;
use sync_ls::transport::{with_stdio_transport, MirrorArgs};
use sync_ls::{
internal_error, DapBuilder, DapMessage, LspBuilder, LspClientRoot, LspMessage, LspResult,
RequestId,
internal_error, DapBuilder, DapMessage, GetMessageKind, LsHook, LspBuilder, LspClientRoot,
LspMessage, LspResult, Message, RequestId, TConnectionTx,
};
use tinymist::tool::project::{compile_main, generate_script_main, project_main, task_main};
use tinymist::tool::testing::{coverage_main, test_main};
@ -25,10 +28,12 @@ use tinymist::{Config, DapRegularInit, RegularInit, ServerState, SuperInit, User
use tinymist_core::LONG_VERSION;
use tinymist_project::EntryResolver;
use tinymist_query::package::PackageInfo;
use tinymist_std::hash::{FxBuildHasher, FxHashMap};
use tinymist_std::{bail, error::prelude::*};
#[cfg(feature = "l10n")]
use tinymist_l10n::{load_translations, set_translations};
use typst::ecow::EcoString;
use crate::args::*;
@ -140,7 +145,7 @@ pub fn lsp_main(args: LspArgs) -> Result<()> {
let is_replay = !args.mirror.replay.is_empty();
with_stdio_transport::<LspMessage>(args.mirror.clone(), |conn| {
let client = LspClientRoot::new(RUNTIMES.tokio_runtime.handle().clone(), conn.sender);
let client = client_root(conn.sender);
ServerState::install_lsp(LspBuilder::new(
RegularInit {
client: client.weak().to_typed(),
@ -168,7 +173,7 @@ pub fn dap_main(args: DapArgs) -> Result<()> {
let is_replay = !args.mirror.replay.is_empty();
with_stdio_transport::<DapMessage>(args.mirror.clone(), |conn| {
let client = LspClientRoot::new(RUNTIMES.tokio_runtime.handle().clone(), conn.sender);
let client = client_root(conn.sender);
ServerState::install_dap(DapBuilder::new(
DapRegularInit {
client: client.weak().to_typed(),
@ -204,7 +209,7 @@ pub fn trace_lsp_main(args: TraceLspArgs) -> Result<()> {
}
with_stdio_transport::<LspMessage>(args.mirror.clone(), |conn| {
let client_root = LspClientRoot::new(RUNTIMES.tokio_runtime.handle().clone(), conn.sender);
let client_root = client_root(conn.sender);
let client = client_root.weak();
let roots = vec![ImmutPath::from(root_path)];
let config = Config {
@ -265,7 +270,7 @@ pub fn query_main(cmds: QueryCommands) -> Result<()> {
use tinymist_project::package::PackageRegistry;
with_stdio_transport::<LspMessage>(MirrorArgs::default(), |conn| {
let client_root = LspClientRoot::new(RUNTIMES.tokio_runtime.handle().clone(), conn.sender);
let client_root = client_root(conn.sender);
let client = client_root.weak();
// todo: roots, inputs, font_opts
@ -336,3 +341,66 @@ pub fn query_main(cmds: QueryCommands) -> Result<()> {
Ok(())
}
/// Creates a new language server host.
fn client_root<M: TryFrom<Message, Error = anyhow::Error> + GetMessageKind>(
sender: TConnectionTx<M>,
) -> LspClientRoot {
LspClientRoot::new(RUNTIMES.tokio_runtime.handle().clone(), sender)
.with_hook(Arc::new(TypstLsHook::default()))
}
#[derive(Default)]
struct TypstLsHook(Mutex<FxHashMap<RequestId, typst_timing::TimingScope>>);
impl fmt::Debug for TypstLsHook {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TypstLsHook").finish()
}
}
impl LsHook for TypstLsHook {
fn start_request(&self, req_id: &RequestId, method: &str) {
().start_request(req_id, method);
if let Some(scope) = typst_timing::TimingScope::new(static_str(method)) {
let mut map = self.0.lock();
map.insert(req_id.clone(), scope);
}
}
fn stop_request(&self, req_id: &RequestId, method: &str, received_at: std::time::Instant) {
().stop_request(req_id, method, received_at);
if let Some(scope) = self.0.lock().remove(req_id) {
let _ = scope;
}
}
fn start_notification(&self, method: &str) {
().start_notification(method);
}
fn stop_notification(
&self,
method: &str,
received_at: std::time::Instant,
result: LspResult<()>,
) {
().stop_notification(method, received_at, result);
}
}
fn static_str(s: &str) -> &'static str {
static STRS: Mutex<FxHashMap<EcoString, &'static str>> =
Mutex::new(HashMap::with_hasher(FxBuildHasher));
let mut strs = STRS.lock();
if let Some(&s) = strs.get(s) {
return s;
}
let static_ref: &'static str = String::from(s).leak();
strs.insert(static_ref.into(), static_ref);
static_ref
}

View file

@ -21,7 +21,7 @@ use crate::project::{
ProjectState, PROJECT_ROUTE_USER_ACTION_PRIORITY,
};
use crate::route::ProjectRouteState;
use crate::task::{ExportTask, FormatTask, UserActionTask};
use crate::task::{ExportTask, FormatTask, ServerTraceTask, UserActionTask};
use crate::world::TaskInputs;
use crate::{lsp::init::*, *};
@ -82,6 +82,8 @@ pub struct ServerState {
pub ever_focusing_by_activities: bool,
/// The client ever sent manual focusing request.
pub ever_manual_focusing: bool,
/// The running server trace.
pub server_trace: Option<ServerTraceTask>,
// Configurations
/// User configuration from the editor.
@ -130,6 +132,7 @@ impl ServerState {
ever_manual_focusing: false,
sema_tokens_registered: false,
formatter_registered: false,
server_trace: None,
config,
pinning_by_user: false,
@ -277,6 +280,8 @@ impl ServerState {
.with_command("tinymist.doGetTemplateEntry", State::get_template_entry)
.with_command_("tinymist.interactCodeContext", State::interact_code_context)
.with_command("tinymist.getDocumentTrace", State::get_document_trace)
.with_command("tinymist.startServerProfiling", State::start_server_trace)
.with_command("tinymist.stopServerProfiling", State::stop_server_trace)
.with_command_("tinymist.getDocumentMetrics", State::get_document_metrics)
.with_command_("tinymist.getWorkspaceLabels", State::get_workspace_labels)
.with_command_("tinymist.getServerInfo", State::get_server_info)

View file

@ -1,20 +1,28 @@
//! The actor that runs user actions.
use std::future::Future;
use std::path::PathBuf;
use std::sync::atomic::AtomicU64;
use std::sync::Arc;
use anyhow::bail;
use base64::Engine;
use futures::FutureExt;
use hyper::body::Bytes;
use hyper::service::service_fn;
use hyper_util::{rt::TokioIo, server::graceful::GracefulShutdown};
use reflexo_typst::vfs::WorkspaceResolver;
use reflexo_typst::{TypstDict, TypstPagedDocument};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use sync_ls::{just_future, LspClient, RequestId, SchedulableResponse};
use serde_json::{json, Value as JsonValue};
use sync_ls::{just_future, LspClient, LspResult, RequestId, SchedulableResponse};
use tinymist_std::error::IgnoreLogging;
use tokio::sync::{mpsc, oneshot};
use tokio_util::sync::CancellationToken;
use typst::{syntax::Span, World};
use crate::project::LspWorld;
use crate::{internal_error, ServerState};
use crate::{internal_error, AliveLock, ConnWithCancel, ServerState};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@ -32,8 +40,8 @@ pub struct TraceParams {
pub struct UserActionTask;
impl UserActionTask {
/// Run a trace.
pub fn trace(&self, params: TraceParams) -> SchedulableResponse<JsonValue> {
/// Traces a specific document.
pub fn trace_document(&self, params: TraceParams) -> SchedulableResponse<JsonValue> {
just_future(async move {
run_trace_program(params)
.await
@ -41,6 +49,66 @@ impl UserActionTask {
})
}
/// Traces the entire server.
pub fn trace_server(&self) -> (ServerTraceTask, SchedulableResponse<JsonValue>) {
let (stop_tx, mut stop_rx) = mpsc::unbounded_channel();
let (resp_tx, resp_rx) = oneshot::channel();
let (addr_tx, addr_rx) = oneshot::channel();
let stop_tx2 = stop_tx.clone();
let task = ServerTraceTask { stop_tx, resp_rx };
typst_timing::enable();
// Empty trace array is not legal, so we add a root scope.
let _scope = typst_timing::TimingScope::new("server_trace");
let timings = async move {
log::info!("before generate timings");
stop_rx.recv().await;
drop(_scope);
typst_timing::disable();
let mut writer = std::io::BufWriter::new(Vec::new());
let res = typst_timing::export_json(&mut writer, |span| {
// todo: resolve line correctly
let file_id = Span::from_raw(span).id();
(WorkspaceResolver::display(file_id).to_string(), 0)
});
let timings = writer.into_inner().unwrap();
log::info!("after generate timings {res:?}");
log::info!("timings: {:?}", std::str::from_utf8(&timings));
resp_tx
.send(Ok(json!({})))
.ok()
.log_error("failed to send response");
Bytes::from_owner(timings)
};
log::info!("now make http server");
let resp = just_future(async move {
let static_file_addr = "127.0.0.1:0".to_owned();
tokio::spawn(async move {
make_http_server(timings, static_file_addr, addr_tx).await;
stop_tx2.send(()).ok();
});
let addr = addr_rx.await.map_err(|err| {
log::error!("failed to get address of trace server: {err:?}");
internal_error("failed to get address of trace server")
})?;
log::info!("trace server has started at {addr}");
Ok(serde_json::json!({
"tracingUrl": format!("http://{addr}"), // not used
}))
});
(task, resp)
}
/// Run a trace request in subprocess.
pub async fn trace_main(
client: LspClient,
@ -100,7 +168,7 @@ async fn run_trace_program(params: TraceParams) -> anyhow::Result<JsonValue> {
let stdout = child.stdout.take().expect("stdout missing");
let (msg_tx, msg_rx) = tokio::sync::oneshot::channel();
let (msg_tx, msg_rx) = oneshot::channel();
std::thread::spawn(move || {
let mut input_chan = std::io::BufReader::new(stdout);
let mut has_response = false;
@ -197,10 +265,12 @@ async fn trace_main(
});
}
"http" => {
let (addr_tx, addr_rx) = tokio::sync::oneshot::channel();
let (addr_tx, addr_rx) = oneshot::channel();
let t = tokio::spawn(async move {
let static_file_addr = "127.0.0.1:0".to_owned();
let timings = async { Bytes::from_owner(timings) };
make_http_server(timings, static_file_addr, addr_tx).await;
std::process::exit(0);
});
let addr = addr_rx.await.unwrap();
@ -223,26 +293,43 @@ async fn trace_main(
std::process::exit(0);
}
/// The server trace task.
pub struct ServerTraceTask {
/// The sender to stop the trace.
pub stop_tx: mpsc::UnboundedSender<()>,
/// The receiver to get the trace result.
pub resp_rx: oneshot::Receiver<LspResult<JsonValue>>,
}
// todo: reuse code from tools preview
/// Create a http server for the trace program.
pub async fn make_http_server(
timings: Vec<u8>,
async fn make_http_server(
timings: impl Future<Output = Bytes> + Send + Sync + 'static,
static_file_addr: String,
addr_tx: tokio::sync::oneshot::Sender<std::net::SocketAddr>,
) -> ! {
addr_tx: oneshot::Sender<std::net::SocketAddr>,
) {
use http_body_util::Full;
use hyper::body::{Bytes, Incoming};
type Server = hyper_util::server::conn::auto::Builder<hyper_util::rt::TokioExecutor>;
let alive_cnt = Arc::<AtomicU64>::default();
let (alive_tx, mut alive_rx) = tokio::sync::mpsc::unbounded_channel();
let timings = hyper::body::Bytes::from(timings);
let make_service = move || {
let timings = timings.shared();
let alive_cnt2 = alive_cnt.clone();
let make_service = move |cancel: CancellationToken| {
let alive_cnt = alive_cnt2.clone();
let timings = timings.clone();
let alive_tx = alive_tx.clone();
service_fn(move |req: hyper::Request<Incoming>| {
let cancel = cancel.clone();
let alive_cnt = alive_cnt.clone();
let timings = timings.clone();
let _ = alive_tx.send(());
async move {
let _alive_cnt = AliveLock::hold(alive_cnt);
// Make sure VSCode can connect to this http server but no malicious website a
// user might open in a browser. We recognize VSCode by an `Origin` header that
// starts with `vscode-webview://`. Malicious websites can (hopefully) not trick
@ -263,6 +350,14 @@ pub async fn make_http_server(
let b = hyper::Response::builder()
.header(hyper::header::ACCESS_CONTROL_ALLOW_ORIGIN, allowed_origin);
if req.uri().path() == "/" {
let timings = tokio::select! {
_ = cancel.cancelled() => {
log::info!("client connection is dropped, exiting loop");
anyhow::bail!("client connection is dropped")
},
timings = timings => timings,
};
let res = if req.method() == hyper::Method::HEAD {
b.body(Full::<Bytes>::default()).unwrap()
} else {
@ -291,7 +386,7 @@ pub async fn make_http_server(
let addr = listener.local_addr().unwrap();
log::info!("trace server listening on http://{addr}");
let (final_tx, final_rx) = tokio::sync::oneshot::channel();
let (final_tx, final_rx) = oneshot::channel();
// the graceful watcher
let graceful = hyper_util::server::graceful::GracefulShutdown::new();
@ -305,7 +400,9 @@ pub async fn make_http_server(
}
};
let conn = server.serve_connection(TokioIo::new(stream), make_service());
let conn = ConnWithCancel::new(stream);
let cancel = conn.cancel.clone();
let conn = server.serve_connection(TokioIo::new(conn), make_service(cancel));
let conn = graceful.watch(conn.into_owned());
tokio::spawn(async move {
conn.await.log_error("cannot serve http");
@ -350,9 +447,14 @@ pub async fn make_http_server(
break;
},
_ = tokio::time::sleep(reflexo::time::Duration::from_secs(15)) => {
log::info!("trace-server: No activity for 15 seconds, shutting down");
final_tx.send(()).ok();
break;
let held = alive_cnt.load(std::sync::atomic::Ordering::SeqCst);
if held == 0 {
log::info!("trace-server: No activity for 15 seconds, shutting down");
final_tx.send(()).ok();
break;
} else {
log::info!("trace-server: still {held} active connections");
}
},
_ = alive_rx.recv() => {
log::info!("trace-server: Activity detected, resetting timer");
@ -363,7 +465,6 @@ pub async fn make_http_server(
addr_tx.send(addr).ok();
join.await.unwrap();
std::process::exit(0);
}
/// Turns a span into a (file, line) pair.

View file

@ -1,5 +1,13 @@
use core::fmt;
use std::pin::Pin;
use std::sync::atomic::AtomicU64;
use std::sync::Arc;
use std::task::{Context, Poll};
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use tokio::net::TcpStream;
#[derive(Clone)]
pub struct Derived<T>(pub T);
@ -44,6 +52,7 @@ macro_rules! get_arg_or_default {
}};
}
pub(crate) use get_arg_or_default;
use tokio_util::sync::CancellationToken;
pub fn try_<T>(f: impl FnOnce() -> Option<T>) -> Option<T> {
f()
@ -60,3 +69,75 @@ pub fn exit_on_ctrl_c() {
std::process::exit(0);
});
}
#[derive(Default)]
pub(crate) struct AliveLock(Arc<AtomicU64>);
impl AliveLock {
pub fn hold(cnt: Arc<AtomicU64>) -> Self {
let held = cnt.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
log::info!("alive lock held, count: {held}");
Self(cnt.clone())
}
}
impl Drop for AliveLock {
fn drop(&mut self) {
let cnt = self.0.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
log::info!("alive lock dropped, count: {cnt}");
}
}
pub(crate) struct ConnWithCancel {
stream: TcpStream,
pub cancel: CancellationToken,
}
impl ConnWithCancel {
pub fn new(stream: TcpStream) -> Self {
Self {
stream,
cancel: CancellationToken::new(),
}
}
}
impl Drop for ConnWithCancel {
fn drop(&mut self) {
self.cancel.cancel()
}
}
impl AsyncRead for ConnWithCancel {
fn poll_read(
self: Pin<&mut Self>,
context: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<tokio::io::Result<()>> {
Pin::new(&mut Pin::into_inner(self).stream).poll_read(context, buf)
}
}
impl AsyncWrite for ConnWithCancel {
fn poll_write(
self: Pin<&mut Self>,
context: &mut Context<'_>,
buf: &[u8],
) -> Poll<Result<usize, tokio::io::Error>> {
Pin::new(&mut Pin::into_inner(self).stream).poll_write(context, buf)
}
fn poll_flush(
self: Pin<&mut Self>,
context: &mut Context<'_>,
) -> Poll<Result<(), tokio::io::Error>> {
Pin::new(&mut Pin::into_inner(self).stream).poll_flush(context)
}
fn poll_shutdown(
self: Pin<&mut Self>,
context: &mut Context<'_>,
) -> Poll<Result<(), tokio::io::Error>> {
Pin::new(&mut Pin::into_inner(self).stream).poll_shutdown(context)
}
}

View file

@ -12,6 +12,8 @@ rust-version.workspace = true
[dependencies]
typst.workspace = true
typst-macros.workspace = true
typst-timing.workspace = true
tinymist-assets.workspace = true
tinymist-std.workspace = true
typst-assets.workspace = true

View file

@ -151,15 +151,7 @@ impl RenderActor {
continue;
};
let data = if has_full_render {
if let Some(data) = self.renderer.pack_current() {
data
} else {
self.renderer.pack_delta(&document)
}
} else {
self.renderer.pack_delta(&document)
};
let data = self.render(has_full_render, &document);
let Ok(_) = self.svg_sender.send(data) else {
log::info!("RenderActor: svg_sender is dropped");
break;
@ -168,6 +160,28 @@ impl RenderActor {
log::info!("RenderActor: exiting")
}
fn render(&mut self, has_full_render: bool, document: &TypstDocument) -> Vec<u8> {
if has_full_render {
if let Some(data) = self.render_full() {
data
} else {
self.render_delta(document)
}
} else {
self.render_delta(document)
}
}
#[typst_macros::time]
fn render_full(&mut self) -> Option<Vec<u8>> {
self.renderer.pack_current()
}
#[typst_macros::time]
fn render_delta(&mut self, document: &TypstDocument) -> Vec<u8> {
self.renderer.pack_delta(document)
}
fn view(&self) -> Option<Arc<dyn CompileView>> {
self.view.read().clone()
}

View file

@ -115,6 +115,7 @@ impl<
}
Some(svg) = self.svg_receiver.recv() => {
log::trace!("WebviewActor: received svg from renderer");
let _scope = typst_timing::TimingScope::new("webview_actor_send_svg");
self.webview_websocket_conn.send(Message::Binary(svg))
.await.log_error("WebViewActor");
}

View file

@ -139,6 +139,7 @@ struct OutlineItem {
children: Vec<OutlineItem>,
}
#[typst_macros::time]
pub fn outline(interner: &mut SpanInternerImpl, document: &TypstDocument) -> Outline {
let outline = get_outline(document.introspector());
let mut items = Vec::with_capacity(outline.as_ref().map_or(0, Vec::len));

View file

@ -1211,6 +1211,11 @@
"title": "Profile coverage of the current test module",
"category": "Typst"
},
{
"command": "tinymist.profileServer",
"title": "Profile the entire language server",
"category": "Typst"
},
{
"command": "tinymist.syncLabel",
"title": "%extension.tinymist.command.tinymist.syncLabel%",

View file

@ -570,7 +570,10 @@ async function commandRunCodeLens(...args: string[]): Promise<void> {
async function codeLensMore(): Promise<void> {
const kBrowsing = "Browsing Preview Documents";
const kPreviewIn = "Preview in ..";
const moreCodeLens = [{ label: kBrowsing }, { label: kPreviewIn }, ...quickExports] as const;
const kProfileServer = "Profile Server";
const moreCodeLensOthers_ = [kBrowsing, kPreviewIn, kProfileServer] as const;
const moreCodeLensOthers = moreCodeLensOthers_.map((label) => ({ label }));
const moreCodeLens = [...moreCodeLensOthers, ...quickExports] as const;
const moreAction = (await vscode.window.showQuickPick(moreCodeLens, {
title: "More Actions",
@ -612,6 +615,10 @@ async function commandRunCodeLens(...args: string[]): Promise<void> {
await commandShow(moreAction.exportKind, moreAction.extraOpts);
return;
}
case kProfileServer: {
void vscode.commands.executeCommand(`tinymist.profileServer`);
return;
}
}
}
}

View file

@ -46,6 +46,12 @@ const toolDesc: Partial<Record<EditorToolName, ToolDescriptor>> = {
description: "Profile Current File",
toolId: "tracing",
},
"profile-server": {
command: "tinymist.profileServer",
title: "Profiling Server",
description: "Profile the Language SErver",
toolId: "profile-server",
},
};
export function toolActivate(context: IContext) {
@ -64,6 +70,7 @@ export function toolActivate(context: IContext) {
export type EditorToolName =
| "template-gallery"
| "tracing"
| "profile-server"
| "summary"
| "font-view"
| "symbol-view"
@ -75,6 +82,7 @@ export async function editorTool(context: ExtensionContext, tool: EditorToolName
"font-view": "Font View",
"symbol-view": "Symbol View",
tracing: "Tracing",
"profile-server": "Profile Server",
summary: "Summary",
docs: `@${opts?.pkg?.namespace}/${opts?.pkg?.name}:${opts?.pkg?.version} (Docs)`,
}[tool];
@ -87,7 +95,7 @@ export async function editorTool(context: ExtensionContext, tool: EditorToolName
title,
{
viewColumn: vscode.ViewColumn.Beside,
preserveFocus: tool === "summary" || tool === "tracing",
preserveFocus: tool === "summary" || tool === "tracing" || tool === "profile-server",
}, // Which sides
{
enableScripts: true,
@ -275,6 +283,16 @@ export async function editorToolAt(
await writeFile(path, data as string);
break;
}
case "stopServerProfiling": {
console.log("Stopping server profiling...");
const traceDataTask = await vscode.commands.executeCommand("tinymist.stopServerProfiling");
const traceData = await traceDataTask;
if (!disposed) {
panel.webview.postMessage({ type: "traceData", data: traceData });
}
break;
}
default: {
console.error("Unknown message type", message.type);
break;
@ -326,6 +344,19 @@ export async function editorToolAt(
break;
}
case "profile-server": {
const profileHookPromise = vscode.commands.executeCommand("tinymist.startServerProfiling");
// do that after the html is reloaded
afterReloadHtml = async () => {
const profileHook = await profileHookPromise;
if (!disposed) {
panel.webview.postMessage({ type: "didStartServerProfiling", data: profileHook });
}
};
break;
}
case "summary": {
const fontsExportConfigure = getFontsExportConfigure(context);
const fontsExportConfig = JSON.stringify(fontsExportConfigure.data);

View file

@ -377,7 +377,7 @@ fn e2e() {
});
let hash = replay_log(&tinymist_binary, &root.join("neovim"));
insta::assert_snapshot!(hash, @"siphash128_13:94308cd99e254c72703bcc3e1386a80");
insta::assert_snapshot!(hash, @"siphash128_13:3c4096d8e71eddb2b5a832f0918bdafa");
}
{
@ -388,7 +388,7 @@ fn e2e() {
});
let hash = replay_log(&tinymist_binary, &root.join("vscode"));
insta::assert_snapshot!(hash, @"siphash128_13:bc403d246654d90466ba47cf9057fee2");
insta::assert_snapshot!(hash, @"siphash128_13:4a6d0b2d59178ca4e600985e4cef545");
}
}

View file

@ -3,7 +3,9 @@ import {
LspMessage,
LspNotification,
LspResponse,
traceData as traceReport,
stopServerProfiling,
programTrace,
serverTrace,
} from "../vscode";
import { startModal } from "../components/modal";
import { base64Decode } from "../utils";
@ -12,6 +14,7 @@ const { div, h2, button, iframe, code, br, span } = van.tags;
const ORIGIN = "https://ui.perfetto.dev";
const openTrace = (arrayBuffer: ArrayBuffer, traceUrl?: string) => {
console.log("openTrace", arrayBuffer, traceUrl);
let subWindow = document.getElementById("perfetto") as HTMLIFrameElement;
subWindow.src = ORIGIN;
subWindow.style.display = "block";
@ -51,7 +54,7 @@ const openTrace = (arrayBuffer: ArrayBuffer, traceUrl?: string) => {
url: reopenUrl.toString(),
},
},
ORIGIN
ORIGIN,
);
};
@ -60,7 +63,21 @@ const openTrace = (arrayBuffer: ArrayBuffer, traceUrl?: string) => {
const enc = new TextEncoder();
export const Tracing = () => {
export const extensionArg = <T>(key: string, defaultValue: T): T => {
return key.startsWith(":") ? defaultValue : JSON.parse(base64Decode(key));
};
export const extensionState = <T>(key: string, defaultValue: T) => {
return van.state<T>(extensionArg(key, defaultValue));
};
const enum TracingStage {
CollectingTrace = "Collecting trace",
WaitingForServer = "Waiting for server",
}
export const Tracing = (serverLevelProfiling: boolean) => () => {
console.log("serverLevelProfiling", serverLevelProfiling);
// #tinymist-app.no-wrap
document.getElementById("tinymist-app")?.classList.add("no-wrap");
@ -74,15 +91,34 @@ export const Tracing = () => {
id: "message",
style: "flex: auto",
},
"Collecting trace..."
"Collecting trace...",
),
...(serverLevelProfiling
? [
button(
{
class: "tinymist-button",
style: "flex: auto",
onclick() {
stopServerProfiling();
},
},
"Stop server profiling",
),
]
: []),
button({
id: "open-trace",
class: "tinymist-button",
style: "display: none; flex: auto",
})
}),
);
let stage = serverLevelProfiling ? TracingStage.WaitingForServer : TracingStage.CollectingTrace;
let tracingContent: ArrayBuffer | undefined = undefined;
let msg: string | undefined = undefined;
const since = Date.now();
const collecting = setInterval(async () => {
const message = document.getElementById("message")!;
@ -92,86 +128,133 @@ export const Tracing = () => {
const elapsed = Date.now() - since;
const elapsedAlign = (elapsed / 1000).toFixed(1).padStart(5, " ");
if (traceReport.val) {
// console.log(JSON.stringify(traceReport.val));
// todo: merge together
clearInterval(collecting);
const openTraceButton = document.getElementById(
"open-trace"
) as HTMLButtonElement;
openTraceButton.style.display = "block";
const rep = traceReport.val;
if (serverLevelProfiling) {
await collectServer();
} else {
await collectProgram();
}
// find first response
const firstResponse = rep.messages.find<LspResponse>(
(msg: LspMessage): msg is LspResponse => "id" in msg && msg.id === 0
);
async function collectServer() {
if (stage === TracingStage.WaitingForServer && serverTrace.val) {
stage = TracingStage.CollectingTrace;
const result = serverTrace.val;
(async () => {
tracingContent = await fetchResult(result).catch((e) => {
msg = `Error: ${e.message}`;
return undefined;
});
})();
} else if (tracingContent || msg) {
clearInterval(collecting);
message.innerText = "";
mainWindow.style.display = "none";
const diagnosticsMessage = rep.messages.find<LspNotification>(
(msg: LspMessage): msg is LspNotification =>
"method" in msg && msg.method === "tinymistExt/diagnostics"
);
startModal(
div(
{ style: "margin: 1em 0" },
...((msg?.length || 0) > 0 ? [code(msg), br()] : []),
"Run in ",
elapsedAlign.trim(),
"s.",
),
);
let msg: string;
let tracingContent: ArrayBuffer | undefined = undefined;
if (!firstResponse) {
msg = "No trace data found";
} else if (firstResponse.error) {
msg = `Error: ${firstResponse.error.message}`;
} else {
msg = "";
if (firstResponse.result.tracingData) {
tracingContent = enc.encode(firstResponse.result.tracingData).buffer;
} else if (firstResponse.result.tracingUrl) {
const response = await fetch(firstResponse.result.tracingUrl);
tracingContent = await response.arrayBuffer();
} else {
msg = "No trace data or url found in response";
if (tracingContent) {
openTrace(tracingContent);
}
}
if (!firstResponse) {
message.innerText = "No response found";
return;
}
message.innerText = "";
mainWindow.style.display = "none";
startModal(
div(
{ style: "margin: 1em 0" },
...(msg.length > 0 ? [code(msg), br()] : []),
"Run ",
diffPath(rep.request.root, rep.request.main),
" using ",
shortProgram(rep.request.compilerProgram),
" in ",
elapsedAlign.trim(),
"s, with ",
code(
{
title: base64Decode(rep.stderr),
style: "text-decoration: underline",
},
"logging"
),
".",
optionalInputs(rep.request.inputs),
optionalFontPaths(rep.request.fontPaths)
),
diagReport(diagnosticsMessage?.params) as Node
);
if (tracingContent) {
openTrace(tracingContent);
}
return;
message.innerText = `${stage}... ${elapsedAlign}s`;
}
message.innerText = `Collecting trace... ${elapsedAlign}s`;
async function collectProgram() {
if (programTrace.val) {
// console.log(JSON.stringify(traceReport.val));
clearInterval(collecting);
const openTraceButton = document.getElementById("open-trace") as HTMLButtonElement;
openTraceButton.style.display = "block";
const rep = programTrace.val;
// find first response
const firstResponse = rep.messages.find<LspResponse>(
(msg: LspMessage): msg is LspResponse => "id" in msg && msg.id === 0,
);
const diagnosticsMessage = rep.messages.find<LspNotification>(
(msg: LspMessage): msg is LspNotification =>
"method" in msg && msg.method === "tinymistExt/diagnostics",
);
if (!firstResponse) {
msg = "No trace data found";
} else if (firstResponse.error) {
msg = `Error: ${firstResponse.error.message}`;
} else {
msg = "";
tracingContent = await fetchResult(firstResponse.result).catch((e) => {
msg = `Error: ${e.message}`;
return undefined;
});
}
if (!firstResponse) {
message.innerText = "No response found";
return;
}
message.innerText = "";
mainWindow.style.display = "none";
startModal(
div(
{ style: "margin: 1em 0" },
...((msg?.length || 0) > 0 ? [code(msg), br()] : []),
"Run ",
diffPath(rep.request.root, rep.request.main),
" using ",
shortProgram(rep.request.compilerProgram),
" in ",
elapsedAlign.trim(),
"s, with ",
code(
{
title: base64Decode(rep.stderr),
style: "text-decoration: underline",
},
"logging",
),
".",
optionalInputs(rep.request.inputs),
optionalFontPaths(rep.request.fontPaths),
),
diagReport(diagnosticsMessage?.params) as Node,
);
if (tracingContent) {
openTrace(tracingContent);
}
return;
}
message.innerText = `${stage}... ${elapsedAlign}s`;
}
async function fetchResult(result: any) {
if (result.tracingData) {
return enc.encode(result.tracingData).buffer;
} else if (result.tracingUrl) {
const response = await fetch(result.tracingUrl);
return await response.arrayBuffer();
} else {
throw new Error("No trace data or url found in response");
}
}
}, 100);
return div(
@ -180,7 +263,7 @@ export const Tracing = () => {
id: "perfetto",
style: "display: none; flex: auto; border: none;",
// sandbox: "allow-same-origin",
})
}),
);
};
@ -192,7 +275,7 @@ function diffPath(root: string, main: string): ChildDom {
return code(
code({ style: "color: #2486b9; text-decoration: underline" }, root),
code({ style: "color: #8cc269; text-decoration: underline" }, main)
code({ style: "color: #8cc269; text-decoration: underline" }, main),
);
}
function shortProgram(compilerProgram: string): ChildDom {
@ -200,10 +283,7 @@ function shortProgram(compilerProgram: string): ChildDom {
if (lastPath) {
// trim extension
lastPath = lastPath.replace(/\.[^.]*$/, "");
return code(
{ title: compilerProgram, style: "text-decoration: underline" },
lastPath
);
return code({ title: compilerProgram, style: "text-decoration: underline" }, lastPath);
}
}
function optionalInputs(inputs: any): ChildDom {
@ -238,10 +318,7 @@ function diagReport(diagnostics?: LspNotification["params"]): ChildDom {
}
const pathDiv = div(
code(
{ style: "text-decoration: underline", title: path },
path.split(/[\/\\]/g).pop()
)
code({ style: "text-decoration: underline", title: path }, path.split(/[\/\\]/g).pop()),
);
const diagPre = div(
@ -261,10 +338,10 @@ function diagReport(diagnostics?: LspNotification["params"]): ChildDom {
span(`${d.range.end.line}:${d.range.end.character}`),
" ",
d.message,
"\n"
)
)
)
"\n",
),
),
),
);
diagDivs.push(div(pathDiv, diagPre));
@ -273,6 +350,6 @@ function diagReport(diagnostics?: LspNotification["params"]): ChildDom {
return div(
{ style: "margin-top: 1.5em" },
h2({ style: "margin: 0.4em 0" }, "Diagnostics"),
...diagDivs
...diagDivs,
);
}

View file

@ -7,6 +7,7 @@ import { setupVscodeChannel } from "./vscode";
type PageComponent =
| "template-gallery"
| "tracing"
| "profile-server"
| "summary"
| "diagnostics"
| "symbol-view"

View file

@ -8,7 +8,8 @@ import { FontView } from "./features/font-view";
mainHarness({
"template-gallery": TemplateGallery,
tracing: Tracing,
tracing: Tracing(false),
"profile-server": Tracing(true),
summary: Summary,
diagnostics: Diagnostics,
"font-view": FontView,

View file

@ -54,7 +54,10 @@ export interface StyleAtCursor {
// import { traceDataMock } from "./vscode.trace.mock";
// export const traceData = van.state<TraceReport | undefined>(traceDataMock);
export const traceData = van.state<TraceReport | undefined>(undefined);
export const programTrace = van.state<TraceReport | undefined>(undefined);
export const serverTrace = van.state<any | undefined>(undefined);
export const didStartServerProfiling = van.state<boolean>(false);
export const styleAtCursor = van.state<StyleAtCursor | undefined>(undefined);
@ -66,7 +69,11 @@ export function setupVscodeChannel() {
window.addEventListener("message", (event: any) => {
switch (event.data.type) {
case "traceData": {
traceData.val = event.data.data;
programTrace.val = event.data.data;
break;
}
case "didStartServerProfiling": {
serverTrace.val = event.data.data;
break;
}
case "styleAtCursor": {
@ -101,6 +108,11 @@ export function requestRevealPath(path: string) {
}
}
export function stopServerProfiling() {
if (vscodeAPI?.postMessage) {
vscodeAPI.postMessage({ type: "stopServerProfiling" });
}
}
export interface TextEdit {
range?: undefined;
newText:
@ -135,7 +147,7 @@ export function requestTextEdit(edit: TextEdit) {
navigator.clipboard.writeText(
typeof edit.newText === "string"
? edit.newText
: edit.newText.code || edit.newText.rest || ""
: edit.newText.code || edit.newText.rest || "",
);
}
}