From 2ee656352af59f102fec22985f456d08588fe0b8 Mon Sep 17 00:00:00 2001 From: Goulven CLEC'H Date: Thu, 19 Dec 2024 23:08:08 +0100 Subject: [PATCH] feat(csslsrs): get document symbols (#10) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(csslsrs): get document symbols * fix encoding * hide warnings * it's looking good * support nested selector * rust & vscode implementation * wasm basic implementation * trim ranges * support deprecated tags * CSS variables — why this don't work ? * fix: use git versions * nit: use latest versions instead of git * @media details * benches * e2e test * Update packages/language-server-tests-benchmarks/src/benchmarks/css/document_symbols.bench.ts Co-authored-by: Erika <3019731+Princesseuh@users.noreply.github.com> * fix review * Update packages/language-server-tests-benchmarks/src/benchmarks/css/document_symbols.bench.ts Co-authored-by: Erika <3019731+Princesseuh@users.noreply.github.com> --------- Co-authored-by: Princesseuh <3019731+Princesseuh@users.noreply.github.com> --- Cargo.lock | 284 ++++--- crates/csslsrs/Cargo.toml | 13 +- crates/csslsrs/README.md | 4 +- crates/csslsrs/benches/bench_main.rs | 3 +- .../benches/features/document_symbols.rs | 41 + crates/csslsrs/benches/features/mod.rs | 1 + .../csslsrs/src/features/document_symbols.rs | 208 +++++ crates/csslsrs/src/lib.rs | 1 + crates/csslsrs/src/service.rs | 1 + crates/csslsrs/tests/document_symbols.rs | 731 ++++++++++++++++++ crates/weblsp/Cargo.toml | 4 +- crates/weblsp/src/css.rs | 9 +- crates/weblsp/src/server.rs | 1 + .../benchmarks/document_symbols.bench.ts | 44 ++ .../fixture/document_symbols.css | 15 + .../benchmarks/css/document_symbols.bench.ts | 32 + .../src/tests/css/documentSymbols.test.ts | 16 + .../src/tests/init.test.ts | 1 + 18 files changed, 1251 insertions(+), 158 deletions(-) create mode 100644 crates/csslsrs/benches/features/document_symbols.rs create mode 100644 crates/csslsrs/src/features/document_symbols.rs create mode 100644 crates/csslsrs/tests/document_symbols.rs create mode 100644 packages/benchmark-wasm/benchmarks/document_symbols.bench.ts create mode 100644 packages/language-server-tests-benchmarks/fixture/document_symbols.css create mode 100644 packages/language-server-tests-benchmarks/src/benchmarks/css/document_symbols.bench.ts create mode 100644 packages/language-server-tests-benchmarks/src/tests/css/documentSymbols.test.ts diff --git a/Cargo.lock b/Cargo.lock index 373ed64..9e578ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,14 +65,14 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] name = "biome_console" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c672a9e31e47f8df74549a570ea3245a93ce3404115c724bb16762fcbbfe17e1" +checksum = "678dfa9a976d1978c136ecddcdf9e9f4947aee7ef2003b4b988dfc78606bfd79" dependencies = [ "biome_markup", "biome_text_size", @@ -85,9 +85,9 @@ dependencies = [ [[package]] name = "biome_css_factory" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f2746e735561c1cb84d74d5432c61aba63ea0738088cd1421d220210cc0d8f" +checksum = "5a71e647ee0b1d3ba57813709fcab49482ec5ddf93bfc146ed1109efdf3eced8" dependencies = [ "biome_css_syntax", "biome_rowan", @@ -95,9 +95,9 @@ dependencies = [ [[package]] name = "biome_css_parser" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6198a1b409476b23e21c6337b4f21bf5ac13f063b681f327ab2f09abcc332094" +checksum = "e7aa0c19ecbde0fce26cf2baa201e234330e2ca9786ad19b7c0634b024a64bcb" dependencies = [ "biome_console", "biome_css_factory", @@ -107,24 +107,24 @@ dependencies = [ "biome_rowan", "biome_unicode_table", "tracing", - "unicode-bom", ] [[package]] name = "biome_css_syntax" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "017a986fc3a12aacfef25a66f84dc1430c6c4cea9bb57d936a101c1b7d9be688" +checksum = "456a8f0447f0ad2522668a3b30a89d2fb9f34837474025f59e58487df589ab96" dependencies = [ "biome_rowan", + "biome_string_case", "serde", ] [[package]] name = "biome_diagnostics" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe1317b6d610541c4e6a0e1f803a946f153ace3468bbc77a8f273dcb04ee526f" +checksum = "ef09f4f5e519a73f1a36c0ffbf0a68ac844bf1a6eb3e9ced215a3d0c5b1319e7" dependencies = [ "backtrace", "biome_console", @@ -133,10 +133,12 @@ dependencies = [ "biome_rowan", "biome_text_edit", "biome_text_size", - "bitflags 2.6.0", "bpaf", + "enumflags2", "oxc_resolver", "serde", + "serde_ini", + "serde_json", "termcolor", "unicode-width", ] @@ -176,37 +178,45 @@ dependencies = [ [[package]] name = "biome_parser" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955dd999f32c086371d5c0e64b4ea1a50f50c98f1f31a3b9fe17ef47198de19b" +checksum = "811bb6840896a74483426ec61b9727731d6222caa3c09a0440c20d662cd1367e" dependencies = [ "biome_console", "biome_diagnostics", "biome_rowan", - "bitflags 2.6.0", + "biome_unicode_table", "drop_bomb", + "enumflags2", + "unicode-bom", ] [[package]] name = "biome_rowan" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3c2dc25a7ba6ae89526340034abed6c89fac35b79060786771e32ed4aac77e7" +checksum = "5ee4442bc598e3baead7453c4070b8b326f3b54287a583105d6ffd13c28b3ef0" dependencies = [ "biome_text_edit", "biome_text_size", "countme", - "hashbrown 0.12.3", - "memoffset", - "rustc-hash 1.1.0", + "hashbrown 0.14.5", + "rustc-hash", + "serde", "tracing", ] +[[package]] +name = "biome_string_case" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5868798da491b19a5b27a0bad5d8727e1e65060fa2dac360b382df00ff520774" + [[package]] name = "biome_text_edit" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d486fdd96d5dad6428213ce64e6b9eb5bfb2fce6387fe901e844d386283de509" +checksum = "a5b42189daa66f0dd20af2a4dd405c28ccf4f5a687c08a5fa4d8e1b31ce2295b" dependencies = [ "biome_text_size", "serde", @@ -215,18 +225,19 @@ dependencies = [ [[package]] name = "biome_text_size" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ec604d15cefdced636255400359aeacfdea5d1e79445efc7aa32a0de7f0319b" +checksum = "672627531edd258f1a9ecdd9bd5ff3ea6c36768622ce2cedc12dc03cb51605c9" dependencies = [ + "schemars", "serde", ] [[package]] name = "biome_unicode_table" -version = "0.5.7" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e8604d34b02180a58af1dbdaac166f1805f27f5370934142a3246f83870952" +checksum = "cb9696fda489e25051248bad5a73bdd53f8d063dc3a7f4a71d4c6aadf6fbcb18" [[package]] name = "bitflags" @@ -262,9 +273,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" +checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8" dependencies = [ "memchr", "regex-automata", @@ -371,12 +382,12 @@ dependencies = [ [[package]] name = "colored" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -433,18 +444,18 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.13" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -461,9 +472,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" @@ -482,7 +493,8 @@ dependencies = [ "console_error_panic_hook", "lsp-types", "palette", - "rustc-hash 2.1.0", + "pretty_assertions", + "rustc-hash", "serde", "serde-wasm-bindgen", "serde_json", @@ -503,6 +515,12 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "drop_bomb" version = "0.1.5" @@ -527,6 +545,26 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "enumflags2" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" +dependencies = [ + "enumflags2_derive", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -564,12 +602,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.14.5" @@ -652,9 +684,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.167" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "lock_api" @@ -703,20 +735,11 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "memoffset" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" -dependencies = [ - "autocfg", -] - [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "a2ef2593ffb6958c941575cee70c8e257438749971869c4ae5acf6f91a168a61" dependencies = [ "adler2", ] @@ -763,7 +786,7 @@ dependencies = [ "indexmap", "json-strip-comments", "once_cell", - "rustc-hash 2.1.0", + "rustc-hash", "serde", "serde_json", "simdutf8", @@ -803,7 +826,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -858,6 +881,16 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -921,9 +954,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ "bitflags 2.6.0", ] @@ -958,16 +991,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] -name = "rustc-demangle" -version = "0.1.24" +name = "result" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "194d8e591e405d1eecf28819740abed6d719d1a2db87fc0bcdedee9a26d55560" [[package]] -name = "rustc-hash" -version = "1.1.0" +name = "rustc-demangle" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" @@ -997,9 +1030,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" dependencies = [ "dyn-clone", + "indexmap", "schemars_derive", "serde", "serde_json", + "smallvec", ] [[package]] @@ -1022,9 +1057,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] @@ -1042,9 +1077,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", @@ -1062,6 +1097,17 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "serde_ini" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb236687e2bb073a7521c021949be944641e671b8505a94069ca37b656c81139" +dependencies = [ + "result", + "serde", + "void", +] + [[package]] name = "serde_json" version = "1.0.133" @@ -1236,6 +1282,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + [[package]] name = "walkdir" version = "2.5.0" @@ -1330,22 +1382,13 @@ dependencies = [ "windows-sys 0.59.0", ] -[[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.6", + "windows-targets", ] [[package]] @@ -1354,22 +1397,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", -] - -[[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", + "windows-targets", ] [[package]] @@ -1378,46 +1406,28 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[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.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[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.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[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.6" @@ -1430,36 +1440,18 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[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.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[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.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[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.6" @@ -1468,12 +1460,12 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" +name = "yansi" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" diff --git a/crates/csslsrs/Cargo.toml b/crates/csslsrs/Cargo.toml index ed87b46..889a061 100644 --- a/crates/csslsrs/Cargo.toml +++ b/crates/csslsrs/Cargo.toml @@ -13,20 +13,21 @@ crate-type = ["cdylib", "rlib"] wasm = ["wasm-bindgen", "serde-wasm-bindgen", "console_error_panic_hook"] [dependencies] -biome_css_parser = "0.5" +biome_css_parser = "0.5.8" lsp-types = "0.97" serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0.132" +serde_json = "1.0" +biome_css_syntax = "0.5.8" +biome_rowan = "0.5.8" +rustc-hash = "2.1.0" +palette = { git = "https://github.com/Ogeon/palette/", rev = "234309cdd2f96ac04f034f963c018b98065dcfd1", version = "0.7.6" } console_error_panic_hook = { version = "0.1.7", optional = true } serde-wasm-bindgen = { version = "0.6", optional = true } wasm-bindgen = { version = "0.2", optional = true } -biome_css_syntax = "0.5.7" -biome_rowan = "0.5.7" -rustc-hash = "2.0.0" -palette = { git = "https://github.com/Ogeon/palette/", rev = "234309cdd2f96ac04f034f963c018b98065dcfd1", version = "0.7.6" } [dev-dependencies] criterion = { package = "codspeed-criterion-compat", version = "2.7.2" } +pretty_assertions = "1.4.1" [[bench]] name = "bench_main" diff --git a/crates/csslsrs/README.md b/crates/csslsrs/README.md index dcbe0b8..dafaf92 100644 --- a/crates/csslsrs/README.md +++ b/crates/csslsrs/README.md @@ -16,7 +16,7 @@ A CSS Language Service made with Rust. | Path completion | ❌ | ✅ | - | | Definition | ❌ | ✅ | - | | References | ❌ | ✅ | - | -| Document Symbols | ❌ | ✅ | - | +| Document Symbols | ✅ | ✅ | Supports more symbols | | Document Highlights | ❌ | ✅ | - | | Code Actions | ❌ | ✅ | - | | Code Lens | ❌ | ✅ | - | @@ -27,4 +27,4 @@ A CSS Language Service made with Rust. | Selection Range | ❌ | ✅ | - | | Validation | ❌ | ✅ | - | | Custom data | ❌ | ✅ | - | -| Super-set of CSS | ❌ | ✅ | - | \ No newline at end of file +| Super-set of CSS | ❌ | ✅ | - | diff --git a/crates/csslsrs/benches/bench_main.rs b/crates/csslsrs/benches/bench_main.rs index 8446f14..5bf6df2 100644 --- a/crates/csslsrs/benches/bench_main.rs +++ b/crates/csslsrs/benches/bench_main.rs @@ -5,5 +5,6 @@ mod features; criterion_main!( features::folding::benches, features::color::benches, - features::hover::benches + features::hover::benches, + features::document_symbols::benches ); diff --git a/crates/csslsrs/benches/features/document_symbols.rs b/crates/csslsrs/benches/features/document_symbols.rs new file mode 100644 index 0000000..52611fc --- /dev/null +++ b/crates/csslsrs/benches/features/document_symbols.rs @@ -0,0 +1,41 @@ +use criterion::{criterion_group, Criterion}; +use csslsrs::service::LanguageService; +use lsp_types::{TextDocumentItem, Uri}; +use std::{hint::black_box, str::FromStr}; + +static TEST_CASE: &str = r#" +body { + background-color: #fff; +} + +a { + color: red; +} + +h1.foo { + color: rgba(0, 0, 0, 0.5); +} + +h1 > span { + color: linear-gradient(to right, red, #fff); +} +"#; + +fn get_document_symbols_benchmark(c: &mut Criterion) { + let mut ls = LanguageService::default(); + + let document = TextDocumentItem { + uri: Uri::from_str("file:///test.css").unwrap(), + language_id: "css".to_string(), + version: 0, + text: TEST_CASE.to_string(), + }; + + ls.upsert_document(document.clone()); + + c.bench_function("get_document_symbols", |b| { + b.iter(|| ls.get_document_symbols(black_box(document.clone()))) + }); +} + +criterion_group!(benches, get_document_symbols_benchmark); diff --git a/crates/csslsrs/benches/features/mod.rs b/crates/csslsrs/benches/features/mod.rs index 14a37f0..424c1e7 100644 --- a/crates/csslsrs/benches/features/mod.rs +++ b/crates/csslsrs/benches/features/mod.rs @@ -1,3 +1,4 @@ pub mod color; +pub mod document_symbols; pub mod folding; pub mod hover; diff --git a/crates/csslsrs/src/features/document_symbols.rs b/crates/csslsrs/src/features/document_symbols.rs new file mode 100644 index 0000000..934cd10 --- /dev/null +++ b/crates/csslsrs/src/features/document_symbols.rs @@ -0,0 +1,208 @@ +use crate::{ + converters::{ + line_index::LineIndex, + to_proto::{position, range}, + PositionEncoding, + }, + css_data::{CssCustomData, Status}, + service::LanguageService, +}; +use biome_css_syntax::{CssLanguage, CssSyntaxKind}; +use biome_rowan::{AstNode, SyntaxNode, TextSize}; +use lsp_types::{DocumentSymbol, Range, SymbolKind, SymbolTag, TextDocumentItem}; + +// From a given CSS node, recursively extract document symbols based on their kind in the CSS Syntax Tree. +fn extract_document_symbols( + node: &SyntaxNode, + line_index: &LineIndex, + encoding: PositionEncoding, + custom_data: &Vec<&CssCustomData>, +) -> Vec { + let mut symbols = Vec::new(); + for child in node.children() { + let symbol: Option = match child.kind() { + // Handle CSS at-rules, e.g. `@media`, `@keyframes`, etc. + CssSyntaxKind::CSS_AT_RULE => child.first_child().and_then(|at_rule| { + at_rule.first_token().map(|token| { + create_symbol( + String::from("@") + token.text_trimmed(), + // For some at-rules, we want to include more details, e.g. `@media` should include the media query list. + at_rule + .first_child() + .filter(|child| child.kind() == CssSyntaxKind::CSS_MEDIA_QUERY_LIST) + .map(|child| child.text_trimmed().to_string()), + SymbolKind::NAMESPACE, + range(line_index, child.text_trimmed_range(), encoding).unwrap(), + Range::new( + position( + line_index, + // We need to include the `@` symbol in the selection range. + token.text_trimmed_range().start() - TextSize::from(1), + encoding, + ) + .unwrap(), + position(line_index, token.text_trimmed_range().end(), encoding) + .unwrap(), + ), + false, + ) + }) + }), + CssSyntaxKind::CSS_GENERIC_PROPERTY => child.children().find_map(|c| { + match c.kind() { + // Handle CSS variables, e.g. `--foo`, `--bar`, etc. + CssSyntaxKind::CSS_DASHED_IDENTIFIER => c.first_token().map(|property_node| { + create_symbol( + property_node.text_trimmed().to_string(), + None, + SymbolKind::VARIABLE, + range(line_index, child.text_trimmed_range(), encoding).unwrap(), + range(line_index, property_node.text_trimmed_range(), encoding) + .unwrap(), + false, + ) + }), + // Handle CSS properties, e.g. `color`, `font-size`, etc. + CssSyntaxKind::CSS_IDENTIFIER => c.first_token().map(|property_node| { + create_symbol( + property_node.text_trimmed().to_string(), + None, + SymbolKind::PROPERTY, + range(line_index, child.text_trimmed_range(), encoding).unwrap(), + range(line_index, property_node.text_trimmed_range(), encoding) + .unwrap(), + is_property_deprecated(property_node.text_trimmed(), custom_data), + ) + }), + _ => None, + } + }), + // Handle CSS selectors, e.g. `.foo`, `#bar`, `div > span`, etc. Even when nested. + CssSyntaxKind::CSS_QUALIFIED_RULE | CssSyntaxKind::CSS_NESTED_QUALIFIED_RULE => child + .children() + .find(|c| { + c.kind() == CssSyntaxKind::CSS_SELECTOR_LIST + || c.kind() == CssSyntaxKind::CSS_SUB_SELECTOR_LIST + || c.kind() == CssSyntaxKind::CSS_RELATIVE_SELECTOR_LIST + }) + .map(|selector| { + create_symbol( + selector.text_trimmed().to_string(), + None, + SymbolKind::CLASS, + range(line_index, child.text_trimmed_range(), encoding).unwrap(), + range(line_index, selector.text_trimmed_range(), encoding).unwrap(), + false, + ) + }), + _ => None, + }; + + // If we have a symbol, search for nested children symbols. + if let Some(mut sym) = symbol { + let children_symbols = + extract_document_symbols(&child, line_index, encoding, custom_data); + if !children_symbols.is_empty() { + sym.children = Some(children_symbols); + } + symbols.push(sym); + } else { + // Even if we don't have a symbol, we still want to search for nested symbols. + let nested_symbols = + extract_document_symbols(&child, line_index, encoding, custom_data); + symbols.extend(nested_symbols); + } + } + + symbols +} + +// Create a LSP `DocumentSymbol` based on the given parameters. +fn create_symbol( + name: String, + detail: Option, + kind: SymbolKind, + range: lsp_types::Range, + selection_range: lsp_types::Range, + is_deprecated: bool, +) -> DocumentSymbol { + // TMP: deprecated is deprecated, but—for now—we still need to intialize it to None, and hide the warning. + #[allow(deprecated)] + DocumentSymbol { + name, + detail, + kind, + tags: is_deprecated.then(|| vec![SymbolTag::DEPRECATED]), + deprecated: None, + range, + selection_range, + children: None, + } +} + +// Use the custom CSS data to determine if a given CSS property is deprecated. +fn is_property_deprecated(property: &str, custom_data: &[&CssCustomData]) -> bool { + custom_data.iter().any(|data| { + data.properties.as_ref().map_or(false, |properties| { + properties + .iter() + .any(|prop| prop.name == property && matches!(prop.status, Some(Status::Obsolete))) + }) + }) +} + +impl LanguageService { + /// Extracts document symbols from the given CSS document. + pub fn get_document_symbols( + &mut self, + document: TextDocumentItem, + ) -> Option> { + let store_entry = self.store.get(&document.uri); + + match store_entry { + Some(store_entry) => Some(extract_document_symbols( + store_entry.css_tree.tree().syntax(), + &store_entry.line_index, + self.options.encoding, + &self.get_css_custom_data(), + )), + None => None, + } + } +} + +#[cfg(feature = "wasm")] +mod wasm_bindings { + use std::str::FromStr; + + use super::extract_document_symbols; + use crate::service::wasm_bindings::WASMLanguageService; + use biome_rowan::AstNode; + use lsp_types::Uri; + use serde_wasm_bindgen; + use wasm_bindgen::prelude::*; + + #[wasm_bindgen(typescript_custom_section)] + const TS_APPEND_CONTENT: &'static str = r#" + export async function get_document_symbols(documentUri: string): import("vscode-languageserver-types").DocumentSymbol[];"#; + + #[wasm_bindgen] + impl WASMLanguageService { + #[wasm_bindgen(skip_typescript, js_name = getDocumentSymbols)] + pub fn get_document_symbols(&self, document_uri: String) -> JsValue { + let store_document = self.store.get(&Uri::from_str(&document_uri).unwrap()); + + let symbols = match store_document { + Some(store_document) => extract_document_symbols( + store_document.css_tree.tree().syntax(), + &store_document.line_index, + self.options.encoding, + &self.get_css_custom_data(), + ), + None => Vec::new(), + }; + + serde_wasm_bindgen::to_value(&symbols).unwrap() + } + } +} diff --git a/crates/csslsrs/src/lib.rs b/crates/csslsrs/src/lib.rs index 1e7d66b..573734a 100644 --- a/crates/csslsrs/src/lib.rs +++ b/crates/csslsrs/src/lib.rs @@ -8,6 +8,7 @@ pub mod store; pub mod features { pub mod color_parser; pub mod colors; + pub mod document_symbols; pub mod folding; pub mod hover; } diff --git a/crates/csslsrs/src/service.rs b/crates/csslsrs/src/service.rs index eef8ee3..527cb0b 100644 --- a/crates/csslsrs/src/service.rs +++ b/crates/csslsrs/src/service.rs @@ -187,6 +187,7 @@ pub mod wasm_bindings { getDocumentColors: typeof get_document_colors; getColorPresentations: typeof get_color_presentations; getFoldingRanges: typeof get_folding_ranges; + getDocumentSymbols: typeof get_document_symbols; free(): void; } "#; diff --git a/crates/csslsrs/tests/document_symbols.rs b/crates/csslsrs/tests/document_symbols.rs new file mode 100644 index 0000000..ad91f38 --- /dev/null +++ b/crates/csslsrs/tests/document_symbols.rs @@ -0,0 +1,731 @@ +#![allow(deprecated)] +// TMP: deprecated is deprecated in Document Symbol, but we still need to intialize it to None, and hide the warning. +use csslsrs::service::LanguageService; +use lsp_types::{DocumentSymbol, Position, Range, SymbolKind, SymbolTag, TextDocumentItem, Uri}; +use pretty_assertions::assert_eq; +use std::str::FromStr; + +fn assert_document_symbols( + ls: &mut LanguageService, + text: &str, + expected_symbols: Vec, +) { + let document = TextDocumentItem { + uri: Uri::from_str("file:///test.css").unwrap(), + language_id: "css".to_string(), + version: 1, + text: text.to_string(), + }; + + ls.upsert_document(document.clone()); + + let symbols = ls.get_document_symbols(document).unwrap_or_default(); + + assert_eq!( + symbols.len(), + expected_symbols.len(), + "Unexpected number of document symbols" + ); + + for (symbol, expected) in symbols.iter().zip(expected_symbols.iter()) { + assert_eq!(symbol, expected, "Unexpected document symbol"); + } +} + +#[test] +fn test_universal_selector() { + let mut ls = LanguageService::default(); + + assert_document_symbols( + &mut ls, + "* {}", + vec![DocumentSymbol { + name: "*".to_string(), + detail: None, + kind: SymbolKind::CLASS, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 4, + }, + }, + selection_range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 1, + }, + }, + children: None, + }], + ); +} + +#[test] +fn test_element_selector() { + let mut ls = LanguageService::default(); + + assert_document_symbols( + &mut ls, + ".foo {}", + vec![DocumentSymbol { + name: ".foo".to_string(), + detail: None, + kind: SymbolKind::CLASS, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 7, + }, + }, + selection_range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 4, + }, + }, + children: None, + }], + ); +} + +#[test] +fn test_compound_selector() { + let mut ls = LanguageService::default(); + + assert_document_symbols( + &mut ls, + "h1.foo {}", + vec![DocumentSymbol { + name: "h1.foo".to_string(), + detail: None, + kind: SymbolKind::CLASS, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 9, + }, + }, + selection_range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 6, + }, + }, + children: None, + }], + ); +} + +#[test] +fn test_descendant_selector() { + let mut ls = LanguageService::default(); + + assert_document_symbols( + &mut ls, + "main h1.foo {}", + vec![DocumentSymbol { + name: "main h1.foo".to_string(), + detail: None, + kind: SymbolKind::CLASS, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 14, + }, + }, + selection_range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 11, + }, + }, + children: None, + }], + ); +} + +#[test] +fn test_complex_selector_in_list() { + let mut ls = LanguageService::default(); + + assert_document_symbols( + &mut ls, + "main > h1.foo, main#bar > button:hover {}", + vec![DocumentSymbol { + name: "main > h1.foo, main#bar > button:hover".to_string(), + detail: None, + kind: SymbolKind::CLASS, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 41, + }, + }, + selection_range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 38, + }, + }, + children: None, + }], + ); +} + +#[test] +fn test_nested_selectors() { + let mut ls = LanguageService::default(); + + assert_document_symbols( + &mut ls, + ".foo { .bar {} }", + vec![DocumentSymbol { + name: ".foo".to_string(), + detail: None, + kind: SymbolKind::CLASS, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 16, + }, + }, + selection_range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 4, + }, + }, + children: Some(vec![DocumentSymbol { + name: ".bar".to_string(), + detail: None, + kind: SymbolKind::CLASS, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 0, + character: 7, + }, + end: Position { + line: 0, + character: 14, + }, + }, + selection_range: Range { + start: Position { + line: 0, + character: 7, + }, + end: Position { + line: 0, + character: 11, + }, + }, + children: None, + }]), + }], + ); +} + +#[test] +fn test_at_rule() { + let mut ls = LanguageService::default(); + + assert_document_symbols( + &mut ls, + "@media (max-width: 768px) {}", + vec![DocumentSymbol { + name: "@media".to_string(), + detail: Some("(max-width: 768px)".to_string()), + kind: SymbolKind::NAMESPACE, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 28, + }, + }, + selection_range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 6, + }, + }, + children: None, + }], + ); +} + +#[test] +fn test_selector_with_properties() { + let mut ls = LanguageService::default(); + + assert_document_symbols( + &mut ls, + ".foo { color: red; text-align: center; }", + vec![DocumentSymbol { + name: ".foo".to_string(), + detail: None, + kind: SymbolKind::CLASS, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 40, + }, + }, + selection_range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 4, + }, + }, + children: Some(vec![ + DocumentSymbol { + name: "color".to_string(), + detail: None, + kind: SymbolKind::PROPERTY, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 0, + character: 7, + }, + end: Position { + line: 0, + character: 17, + }, + }, + selection_range: Range { + start: Position { + line: 0, + character: 7, + }, + end: Position { + line: 0, + character: 12, + }, + }, + children: None, + }, + DocumentSymbol { + name: "text-align".to_string(), + detail: None, + kind: SymbolKind::PROPERTY, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 0, + character: 19, + }, + end: Position { + line: 0, + character: 37, + }, + }, + selection_range: Range { + start: Position { + line: 0, + character: 19, + }, + end: Position { + line: 0, + character: 29, + }, + }, + children: None, + }, + ]), + }], + ); +} + +#[test] +fn test_deprecated_property() { + let mut ls = LanguageService::default(); + + assert_document_symbols( + &mut ls, + ".foo { box-align: center; }", + vec![DocumentSymbol { + name: ".foo".to_string(), + detail: None, + kind: SymbolKind::CLASS, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 27, + }, + }, + selection_range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 4, + }, + }, + children: Some(vec![DocumentSymbol { + name: "box-align".to_string(), + detail: None, + kind: SymbolKind::PROPERTY, + tags: Some(vec![SymbolTag::DEPRECATED]), + deprecated: None, + range: Range { + start: Position { + line: 0, + character: 7, + }, + end: Position { + line: 0, + character: 24, + }, + }, + selection_range: Range { + start: Position { + line: 0, + character: 7, + }, + end: Position { + line: 0, + character: 16, + }, + }, + children: None, + }]), + }], + ); +} + +#[test] +fn test_css_variables() { + let mut ls = LanguageService::default(); + + assert_document_symbols( + &mut ls, + ":root {--color-primary: #333;}h1 {color: var(--color-primary);}", + vec![ + DocumentSymbol { + name: ":root".to_string(), + detail: None, + kind: SymbolKind::CLASS, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 30, + }, + }, + selection_range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 5, + }, + }, + children: Some(vec![DocumentSymbol { + name: "--color-primary".to_string(), + detail: None, + kind: SymbolKind::VARIABLE, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 0, + character: 7, + }, + end: Position { + line: 0, + character: 28, + }, + }, + selection_range: Range { + start: Position { + line: 0, + character: 7, + }, + end: Position { + line: 0, + character: 22, + }, + }, + children: None, + }]), + }, + DocumentSymbol { + name: "h1".to_string(), + detail: None, + kind: SymbolKind::CLASS, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 0, + character: 30, + }, + end: Position { + line: 0, + character: 63, + }, + }, + selection_range: Range { + start: Position { + line: 0, + character: 30, + }, + end: Position { + line: 0, + character: 32, + }, + }, + children: Some(vec![DocumentSymbol { + name: "color".to_string(), + detail: None, + kind: SymbolKind::PROPERTY, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 0, + character: 34, + }, + end: Position { + line: 0, + character: 61, + }, + }, + selection_range: Range { + start: Position { + line: 0, + character: 34, + }, + end: Position { + line: 0, + character: 39, + }, + }, + children: None, + }]), + }, + ], + ); +} + +#[test] +fn test_at_rule_with_nested_properties() { + let mut ls = LanguageService::default(); + + assert_document_symbols( + &mut ls, + "@font-palette-values --my-palette {\n font-family: Bixa;\n base-palette: 1;\n override-colors: 0 #ff0000;\n}", + vec![DocumentSymbol { + name: "@font-palette-values".to_string(), + detail: None, + kind: SymbolKind::NAMESPACE, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 4, + character: 1, + }, + }, + selection_range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 20, + }, + }, + children: Some(vec![ + DocumentSymbol { + name: "font-family".to_string(), + detail: None, + kind: SymbolKind::PROPERTY, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 1, + character: 2, + }, + end: Position { + line: 1, + character: 19, + }, + }, + selection_range: Range { + start: Position { + line: 1, + character: 2, + }, + end: Position { + line: 1, + character: 13, + }, + }, + children: None, + }, + DocumentSymbol { + name: "base-palette".to_string(), + detail: None, + kind: SymbolKind::PROPERTY, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 2, + character: 2, + }, + end: Position { + line: 2, + character: 17, + }, + }, + selection_range: Range { + start: Position { + line: 2, + character: 2, + }, + end: Position { + line: 2, + character: 14, + }, + }, + children: None, + }, + DocumentSymbol { + name: "override-colors".to_string(), + detail: None, + kind: SymbolKind::PROPERTY, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 3, + character: 2, + }, + end: Position { + line: 3, + character: 28, + }, + }, + selection_range: Range { + start: Position { + line: 3, + character: 2, + }, + end: Position { + line: 3, + character: 17, + }, + }, + children: None, + }, + ]), + }], + ); +} diff --git a/crates/weblsp/Cargo.toml b/crates/weblsp/Cargo.toml index 02819b7..df5b589 100644 --- a/crates/weblsp/Cargo.toml +++ b/crates/weblsp/Cargo.toml @@ -8,6 +8,6 @@ license = "MIT" [dependencies] lsp-server = "0.7.7" lsp-types = "0.97.0" -serde = "1.0.215" -serde_json = "1.0.133" +serde = "1.0" +serde_json = "1.0" csslsrs = { version = "0.1.0", path = "../csslsrs" } diff --git a/crates/weblsp/src/css.rs b/crates/weblsp/src/css.rs index 53294d5..a8a4c05 100644 --- a/crates/weblsp/src/css.rs +++ b/crates/weblsp/src/css.rs @@ -1,7 +1,8 @@ use csslsrs::service::LanguageService; use lsp_server::{Connection, Message, Request, Response}; use lsp_types::request::{ - ColorPresentationRequest, DocumentColor, FoldingRangeRequest, HoverRequest, + ColorPresentationRequest, DocumentColor, DocumentSymbolRequest, FoldingRangeRequest, + HoverRequest, }; use std::error::Error; @@ -57,6 +58,12 @@ pub fn handle_request( ); send_result(connection, id, serde_json::to_value(&hover).unwrap())?; } + "textDocument/documentSymbol" => { + let (id, params) = cast::(req)?; + let symbols = language_service + .get_document_symbols(get_text_document(params.text_document, language_service)?); + send_result(connection, id, serde_json::to_value(&symbols).unwrap())?; + } _ => { eprintln!("handle_request: unsupported request: {}", req.method); } diff --git a/crates/weblsp/src/server.rs b/crates/weblsp/src/server.rs index 66c939b..617df84 100644 --- a/crates/weblsp/src/server.rs +++ b/crates/weblsp/src/server.rs @@ -4,6 +4,7 @@ pub(crate) fn get_server_capabilities() -> serde_json::Value { let capabilities = ServerCapabilities { hover_provider: Some(lsp_types::HoverProviderCapability::Simple(true)), color_provider: Some(lsp_types::ColorProviderCapability::Simple(true)), + document_symbol_provider: Some(lsp_types::OneOf::Left(true)), folding_range_provider: Some(lsp_types::FoldingRangeProviderCapability::Simple(true)), text_document_sync: Some(TextDocumentSyncCapability::Kind( lsp_types::TextDocumentSyncKind::FULL, diff --git a/packages/benchmark-wasm/benchmarks/document_symbols.bench.ts b/packages/benchmark-wasm/benchmarks/document_symbols.bench.ts new file mode 100644 index 0000000..4e07901 --- /dev/null +++ b/packages/benchmark-wasm/benchmarks/document_symbols.bench.ts @@ -0,0 +1,44 @@ +import { LanguageService } from "csslsrs"; +import { getCSSLanguageService } from "vscode-css-languageservice"; +import { TextDocument } from "vscode-languageserver-textdocument"; +import { bench, describe } from "vitest"; + +const vscodeLanguageService = getCSSLanguageService(); +const content = ` +body { + background-color: #fff; +} + +a { + color: red; +} + +h1.foo { + color: rgba(0, 0, 0, 0.5); +} + +h1 > span { + color: linear-gradient(to right, red, #fff); +} +`; + +const textDocument = TextDocument.create("file:///test.css", "css", 0, content); +const ls = new LanguageService({ + include_base_css_custom_data: true, +}); +ls.upsertDocument(textDocument); + +describe("Document Symbols", async () => { + bench("CSSLSRS(WASM) - Document Symbols", () => { + ls.getDocumentSymbols(textDocument.uri); + }); + if (!process.env.CODSPEED) { + bench("vscode-css-languageservice - Document Symbols", () => { + const stylesheet = vscodeLanguageService.parseStylesheet(textDocument); + vscodeLanguageService.findDocumentSymbols2( + textDocument, + stylesheet + ); + }); + } +}); diff --git a/packages/language-server-tests-benchmarks/fixture/document_symbols.css b/packages/language-server-tests-benchmarks/fixture/document_symbols.css new file mode 100644 index 0000000..c96a386 --- /dev/null +++ b/packages/language-server-tests-benchmarks/fixture/document_symbols.css @@ -0,0 +1,15 @@ +body { + background-color: #fff; +} + +a { + color: red; +} + +h1.foo { + color: rgba(0, 0, 0, 0.5); +} + +h1 > span { + color: linear-gradient(to right, red, #fff); +} diff --git a/packages/language-server-tests-benchmarks/src/benchmarks/css/document_symbols.bench.ts b/packages/language-server-tests-benchmarks/src/benchmarks/css/document_symbols.bench.ts new file mode 100644 index 0000000..13435c7 --- /dev/null +++ b/packages/language-server-tests-benchmarks/src/benchmarks/css/document_symbols.bench.ts @@ -0,0 +1,32 @@ +import { afterAll, bench, describe } from "vitest"; +import { startLanguageServer } from "../../server"; +import { fileURLToPath } from "url"; + +const filePath = fileURLToPath( + new URL("../../../fixture/document_symbols.css", import.meta.url) +); + +const weblsp = await startLanguageServer(undefined, "weblsp"); +const weblspUri = (await weblsp.openTextDocument(filePath, "css")).uri; + +const vscodeLsp = await startLanguageServer(undefined, "vscode-css"); +const vscodeLspUri = (await vscodeLsp.openTextDocument(filePath, "css")).uri; + +describe("Document Symbols", async () => { + bench("weblsp - Document Symbols", async () => { + await weblsp.sendDocumentSymbolRequest(weblspUri); + }); + + if (!process.env.CODSPEED) { + bench("vscode-css-languageserver - Document Symbols", async () => { + await vscodeLsp.sendDocumentSymbolRequest(vscodeLspUri); + }); + } + + afterAll(async () => { + await weblsp.shutdown(); + await vscodeLsp.shutdown(); + await weblsp.exit(); + await vscodeLsp.exit(); + }); +}); diff --git a/packages/language-server-tests-benchmarks/src/tests/css/documentSymbols.test.ts b/packages/language-server-tests-benchmarks/src/tests/css/documentSymbols.test.ts new file mode 100644 index 0000000..f955cd0 --- /dev/null +++ b/packages/language-server-tests-benchmarks/src/tests/css/documentSymbols.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from "vitest"; + +describe("CSS - Document Symbols", () => { + it("should return symbols for a document", async () => { + const doc = await languageServer.openFakeDocument( + `h1 { color: red; }`, + "css" + ); + + const symbols = await languageServer.sendDocumentSymbolRequest( + doc.uri + ); + + expect(symbols).toBeDefined(); + }); +}); diff --git a/packages/language-server-tests-benchmarks/src/tests/init.test.ts b/packages/language-server-tests-benchmarks/src/tests/init.test.ts index 3b4697a..987e895 100644 --- a/packages/language-server-tests-benchmarks/src/tests/init.test.ts +++ b/packages/language-server-tests-benchmarks/src/tests/init.test.ts @@ -12,6 +12,7 @@ describe("Language server initilization", () => { foldingRangeProvider: true, hoverProvider: true, textDocumentSync: 1, + documentSymbolProvider: true, }; expect(languageServer.initializeResult.capabilities).to.deep.equal(