diff --git a/Cargo.lock b/Cargo.lock index a5cebd3..73f6ddf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,28 +19,28 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "anyhow" -version = "1.0.76" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" [[package]] name = "async-trait" -version = "0.1.75" +version = "0.1.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdf6721fb0140e4f897002dd086c06f6c27775df19cfe1fccb21181a48fd2c98" +checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" dependencies = [ "proc-macro2", "quote", - "syn 2.0.42", + "syn", ] [[package]] @@ -56,27 +56,26 @@ dependencies = [ [[package]] name = "auto_impl" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" +checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ - "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -95,18 +94,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.83" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" [[package]] name = "cfg-if" @@ -158,9 +154,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -172,9 +168,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -182,44 +178,44 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-io" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-macro" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.42", + "syn", ] [[package]] name = "futures-sink" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -256,9 +252,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "httparse" @@ -284,13 +280,13 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jinja-lsp" -version = "0.1.61" +version = "0.1.70" dependencies = [ "anyhow", "env_logger", @@ -308,7 +304,7 @@ dependencies = [ [[package]] name = "jinja-lsp-queries" -version = "0.1.61" +version = "0.1.70" dependencies = [ "ropey", "tower-lsp", @@ -319,9 +315,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.151" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "lock_api" @@ -335,9 +331,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "lsp-types" @@ -354,24 +350,24 @@ dependencies = [ [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "minijinja" -version = "1.0.10" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "208758577ef2c86cf5dd3e85730d161413ec3284e2d73b2ef65d9a24d9971bcb" +checksum = "3fb3bf58a1ec4f3f228bec851a2066c7717ad308817cd8a08f67c10660c6ff7b" dependencies = [ "serde", ] [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] @@ -384,7 +380,7 @@ checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -393,15 +389,15 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.3.3", + "hermit-abi 0.3.9", "libc", ] [[package]] name = "object" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] @@ -432,7 +428,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -443,29 +439,29 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.42", + "syn", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -473,44 +469,20 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro2" -version = "1.0.71" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -526,9 +498,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.2" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", @@ -538,9 +510,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", @@ -549,9 +521,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "ropey" @@ -571,9 +543,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "same-file" @@ -592,29 +564,29 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.42", + "syn", ] [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" dependencies = [ "itoa", "ryu", @@ -623,13 +595,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" +checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.42", + "syn", ] [[package]] @@ -652,18 +624,18 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.2" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -674,20 +646,9 @@ checksum = "e9557cb6521e8d009c51a8666f09356f4b817ba9ba0981a305bd86aee47bd35c" [[package]] name = "syn" -version = "1.0.109" +version = "2.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b7d0a2c048d661a1a59fcd7355baa232f7ed34e0ee4df2eef3c1c1c0d3852d8" +checksum = "11a6ae1e52eb25aab8f3fb9fca13be982a373b8f1157ca14b897a825ba4a2d35" dependencies = [ "proc-macro2", "quote", @@ -696,9 +657,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] @@ -720,9 +681,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.35.1" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -734,7 +695,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -745,7 +706,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.42", + "syn", ] [[package]] @@ -813,7 +774,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa" dependencies = [ "proc-macro2", "quote", - "syn 2.0.42", + "syn", ] [[package]] @@ -841,7 +802,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.42", + "syn", ] [[package]] @@ -865,9 +826,9 @@ dependencies = [ [[package]] name = "tree-sitter-jinja2" -version = "0.0.5" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c01a7e67bf0ea5ed41c90edc788ecc7c45453f590bba2f9684810b7db0e822f" +checksum = "06e13eeb9c161d95d0a4326f84aee63006820b1b5bb0206d86fa347cd2f0258b" dependencies = [ "cc", "tree-sitter", @@ -885,9 +846,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" @@ -897,9 +858,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] @@ -916,17 +877,11 @@ dependencies = [ "serde", ] -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -975,7 +930,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.4", ] [[package]] @@ -984,13 +948,28 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] [[package]] @@ -999,38 +978,80 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" diff --git a/example/src/main.rs b/example/src/main.rs index e8e76ae..daaa8b2 100644 --- a/example/src/main.rs +++ b/example/src/main.rs @@ -9,7 +9,7 @@ fn main() { phone_number => "(123) 456-7890", street => "123 Main St", city => "Dallas", - header_info => "This is some information about the user." + header_info => "This is some information about the user.", }; jinja.add_global("PROJECT_NAME", "Example"); } diff --git a/jinja-lsp-queries/Cargo.toml b/jinja-lsp-queries/Cargo.toml index b7b42a5..fab7465 100644 --- a/jinja-lsp-queries/Cargo.toml +++ b/jinja-lsp-queries/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "jinja-lsp-queries" -version = "0.1.61" +version = "0.1.70" edition = "2021" description = "TreeSitter queries for jinja-lsp" license = "MIT" [dependencies] tree-sitter = "0.20.10" -tree-sitter-jinja2 = "0.0.5" +tree-sitter-jinja2 = "0.0.6" tree-sitter-rust = "0.20.4" tower-lsp = { version = "0.20.0", features = ["proposed"] } ropey = "1.5.0" diff --git a/jinja-lsp-queries/src/capturer/capturer.rs b/jinja-lsp-queries/src/capturer/capturer.rs deleted file mode 100644 index 80c2a6c..0000000 --- a/jinja-lsp-queries/src/capturer/capturer.rs +++ /dev/null @@ -1,277 +0,0 @@ -use std::collections::HashMap; - -use tree_sitter::{Node, Point, QueryCapture}; - -use crate::{ - test_queries::CompletionType, - tree_builder::{IdentifierState, JinjaVariable}, -}; - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct CaptureDetails { - pub start_position: Point, - pub end_position: Point, - pub value: String, -} - -pub trait Capturer { - fn save_by( - &mut self, - capture: &QueryCapture<'_>, - hm: &mut HashMap, - capture_names: &[String], - source: &str, - ); - - fn value(&self, capture: &QueryCapture<'_>, source: &str) -> String { - let value = if let Ok(capture_value) = capture.node.utf8_text(source.as_bytes()) { - capture_value.to_owned() - } else { - "".to_owned() - }; - value - } -} - -#[derive(Default)] -pub struct JinjaInitCapturer { - pub data: bool, - pub state: IdentifierState, - pub states: Vec, -} - -impl JinjaInitCapturer { - pub fn id_exist(&self, capture: &QueryCapture<'_>) -> bool { - let id = capture.node.id(); - self.states.iter().any(|item| item.id == id) - } - - pub fn to_vec(&self) -> Vec { - let mut all = vec![]; - for state in &self.states { - state.keyword.get_data(&mut all, state); - } - all - } -} - -impl Capturer for JinjaInitCapturer { - fn save_by( - &mut self, - capture: &QueryCapture<'_>, - hm: &mut HashMap, - capture_names: &[String], - source: &str, - ) { - let key = capture_names[capture.index as usize].to_owned(); - if key == "start_statement" { - if !self.id_exist(capture) { - let mut state = IdentifierState::default(); - state.parse_start_statement(capture, source); - self.states.push(state); - } - } else if key == "end_statement" { - self.state.parse_end_statement(capture, source); - } - } -} - -#[derive(Default, Debug)] -pub struct JinjaObjectCapturer { - objects: Vec, - dot: (Point, Point), - pipe: (Point, Point), - expr: (Point, Point), - ident: (Point, Point), -} - -impl JinjaObjectCapturer { - pub fn show(&self) -> Vec { - self.objects.clone() - } - - fn add_operator(&mut self, capture: &QueryCapture<'_>, dot: u8) { - let start = capture.node.start_position(); - let end = capture.node.end_position(); - if dot == 0 { - self.dot = (start, end); - } else if dot == 1 { - self.pipe = (start, end); - } else if dot == 2 { - self.expr = (start, end); - } - } - - pub fn in_pipe(&self, trigger_point: Point) -> bool { - trigger_point >= self.pipe.0 && trigger_point <= self.pipe.1 - } - - pub fn in_expr(&self, trigger_point: Point) -> bool { - trigger_point >= self.expr.0 && trigger_point <= self.expr.1 && trigger_point > self.ident.1 - } - - pub fn completion(&self, trigger_point: Point) -> Option { - if self.in_pipe(trigger_point) { - return Some(CompletionType::Pipe); - } else if self.in_expr(trigger_point) { - return Some(CompletionType::Identifier); - } - None - } - - pub fn build_object(&mut self, capture: &QueryCapture<'_>, source: &str) { - let value = capture.node.utf8_text(source.as_bytes()); - let start = capture.node.start_position(); - let end = capture.node.end_position(); - if let Ok(value) = value { - if start.row == self.dot.1.row && start.column == self.dot.1.column { - match self - .objects - .last_mut() - .map(|last| { - last.fields.push((String::from(value), (start, end))); - self.ident = (start, end); - }) - .is_none() - { - true => { - self.objects - .push(JinjaObject::new(String::from(value), start, end)); - self.ident = (start, end); - } - false => (), - } - } else { - self.objects - .push(JinjaObject::new(String::from(value), start, end)); - self.ident = (start, end); - } - } - } -} - -impl Capturer for JinjaObjectCapturer { - fn save_by( - &mut self, - capture: &QueryCapture<'_>, - hm: &mut HashMap, - capture_names: &[String], - source: &str, - ) { - let key = capture_names[capture.index as usize].to_owned(); - if key == "just_id" { - self.build_object(capture, source); - } else if key == "dot" { - self.add_operator(capture, 0); - } else if key == "pipe" { - self.add_operator(capture, 1); - } else if key == "expr" { - self.add_operator(capture, 2); - } - } -} - -#[derive(Default, Debug, Clone)] -pub struct JinjaObject { - name: String, - location: (Point, Point), - fields: Vec<(String, (Point, Point))>, -} - -impl JinjaObject { - pub fn new(name: String, start: Point, end: Point) -> Self { - Self { - name, - location: (start, end), - fields: vec![], - } - } - - pub fn add_field(&mut self, field: String, start: Point, end: Point) { - self.fields.push((field, (start, end))); - } -} - -#[derive(Default, Debug, Clone)] -pub struct RustMacro { - variables: HashMap, - id: usize, -} - -impl RustMacro { - pub fn show(&self) -> &HashMap { - &self.variables - } -} - -#[derive(Default, Debug, Clone)] -pub struct RustCapturer { - macros: HashMap, -} - -impl RustCapturer { - pub fn add_macro(&mut self, capture: &QueryCapture<'_>, source: &str) { - let id = capture.node.id(); - if self.macros.get(&id).is_none() { - let mut context_macro = RustMacro::default(); - let mut walker = capture.node.walk(); - let children = capture.node.children(&mut walker); - let mut current = 0; - for child in children { - match child.kind_id() { - 1 => current = 1, - 55 => current = 2, - 152 => { - if current == 2 { - self.check_token_tree(child, &mut context_macro, source); - self.macros.insert(id, context_macro); - break; - } - } - _ => (), - } - } - } - } - - pub fn check_token_tree( - &mut self, - node: Node<'_>, - context_macro: &mut RustMacro, - source: &str, - ) { - let mut walker = node.walk(); - let children = node.children(&mut walker); - for child in children { - if child.kind_id() == 1 { - let text = child.utf8_text(source.as_bytes()); - if let Ok(id) = text { - if context_macro.variables.get(id).is_none() { - let start = child.start_position(); - let end = child.end_position(); - context_macro.variables.insert(id.to_string(), (start, end)); - } - } - } - } - } - - pub fn show(&self) -> &HashMap { - &self.macros - } -} - -impl Capturer for RustCapturer { - fn save_by( - &mut self, - capture: &QueryCapture<'_>, - hm: &mut HashMap, - capture_names: &[String], - source: &str, - ) { - let key = capture_names[capture.index as usize].to_owned(); - if key == "context_macro" { - self.add_macro(capture, source); - } - } -} diff --git a/jinja-lsp-queries/src/capturer/included.rs b/jinja-lsp-queries/src/capturer/included.rs deleted file mode 100644 index 18ebe44..0000000 --- a/jinja-lsp-queries/src/capturer/included.rs +++ /dev/null @@ -1,172 +0,0 @@ -use tower_lsp::lsp_types::{Position, Range, Url}; -use tree_sitter::Point; - -use super::{object::CompletionType, Capturer}; - -#[derive(Default, Debug)] -pub struct IncludeCapturer { - pub included: Vec, -} - -impl IncludeCapturer { - pub fn in_template(&self, trigger_point: Point) -> Option<&IncludedTemplate> { - if let Some(last) = self.included.last() { - if trigger_point >= last.template.0 && trigger_point <= last.template.1 { - return Some(last); - } - } - None - } - - pub fn find(&self, trigger_point: Point) -> Option<(&IncludedTemplate, Include)> { - if let Some(string) = self.in_template(trigger_point) { - return Some((string, Include::Template)); - } - None - } - - pub fn completion(&self, trigger_point: Point) -> Option { - let part = self.find(trigger_point)?; - let location = part.1.location(trigger_point, part.0); - Some(CompletionType::IncludedTemplate { - name: location.0, - range: location.1, - }) - } - - pub fn add_template(&mut self, name: String, range: (Point, Point)) { - self.included.push(IncludedTemplate { - name, - template: range, - ..Default::default() - }); - } - - pub fn last(&self) -> Option<&String> { - Some(&self.included.last()?.name) - } -} - -impl Capturer for IncludeCapturer { - fn save_by( - &mut self, - capture: &tree_sitter::QueryCapture<'_>, - capture_names: &[String], - source: &str, - ) { - let key = capture_names[capture.index as usize].to_owned(); - if key == "keyword" { - self.add_template("".to_owned(), (Point::default(), Point::default())); - } else if key == "error" { - if let Some(last) = self.included.last_mut() { - let start = capture.node.start_position(); - let end = capture.node.end_position(); - last.error = (start, end); - } - } else if key == "id" { - if let Some(last) = self.included.last_mut() { - let start = capture.node.start_position(); - let end = capture.node.end_position(); - last.identifier = (start, end); - if let Ok(value) = capture.node.utf8_text(source.as_bytes()) { - let name = value.replace(['\'', '\"'], ""); - last.name = name; - } - } - } else if key == "template" { - if let Ok(value) = capture.node.utf8_text(source.as_bytes()) { - if let Some(last) = self.included.last_mut() { - let start = capture.node.start_position(); - let end = capture.node.end_position(); - let name = value.replace(['\'', '\"'], ""); - last.name = name; - last.template = (start, end); - } - } - } - } -} - -#[derive(Default, Debug)] -pub struct IncludedTemplate { - pub name: String, - pub template: (Point, Point), - pub error: (Point, Point), - pub identifier: (Point, Point), -} - -impl IncludedTemplate { - pub fn new(name: &str) -> Self { - Self { - name: name.to_owned(), - template: (Point::default(), Point::default()), - ..Default::default() - } - } - - pub fn is_template(&self, root: &str) -> Option { - let template = format!("{}/{}", root, &self.name); - let template = std::fs::canonicalize(template).ok()?; - let url = format!("file://{}", template.to_str()?); - let uri = Url::parse(&url).ok()?; - Some(uri) - } -} - -#[derive(Debug)] -pub enum Include { - Id, - Template, - Error, -} - -impl Include { - pub fn location(&self, trigger_point: Point, part: &IncludedTemplate) -> (String, Range) { - match self { - Include::Error => { - let range = self.to_range(part.error); - (String::from(""), range) - } - Include::Id => { - let l1 = part.identifier.1.column - trigger_point.column; - if part.name.len() < l1 { - let range = self.to_range(part.identifier); - return (String::from(""), range); - } - let end = part.name.len() - l1; - let mut name = String::new(); - for (i, item) in part.name.char_indices() { - name.push(item); - if i == end { - break; - } - } - let range = self.to_range(part.identifier); - (name, range) - } - Include::Template => { - let l1 = part.template.1.column - trigger_point.column; - if part.name.len() < l1 || part.name.is_empty() { - let range = self.to_range(part.template); - return (String::from(""), range); - } - let end = part.name.len() - l1; - let mut name = String::new(); - for (i, item) in part.name.char_indices() { - name.push(item); - if i == end { - break; - } - } - let range = self.to_range(part.template); - (name, range) - } - } - } - - pub fn to_range(&self, points: (Point, Point)) -> Range { - let start = Position::new(points.0.row as u32, points.0.column as u32); - let end = Position::new(points.1.row as u32, points.1.column as u32); - Range::new(start, end) - } -} diff --git a/jinja-lsp-queries/src/capturer/init.rs b/jinja-lsp-queries/src/capturer/init.rs deleted file mode 100644 index f19d9ba..0000000 --- a/jinja-lsp-queries/src/capturer/init.rs +++ /dev/null @@ -1,42 +0,0 @@ -use tree_sitter::QueryCapture; - -use crate::tree_builder::{IdentifierState, JinjaVariable}; - -use super::Capturer; - -#[derive(Default, Debug)] -pub struct JinjaInitCapturer { - pub data: bool, - pub state: IdentifierState, - pub states: Vec, -} - -impl JinjaInitCapturer { - pub fn id_exist(&self, capture: &QueryCapture<'_>) -> bool { - let id = capture.node.id(); - self.states.iter().any(|item| item.id == id) - } - - pub fn to_vec(&self) -> Vec { - let mut all = vec![]; - for state in &self.states { - state.keyword.get_data(&mut all, state); - } - all - } -} - -impl Capturer for JinjaInitCapturer { - fn save_by(&mut self, capture: &QueryCapture<'_>, capture_names: &[String], source: &str) { - let key = capture_names[capture.index as usize].to_owned(); - if key == "start_statement" { - if !self.id_exist(capture) { - let mut state = IdentifierState::default(); - state.parse_start_statement(capture, source); - self.states.push(state); - } - } else if key == "end_statement" { - self.state.parse_end_statement(capture, source); - } - } -} diff --git a/jinja-lsp-queries/src/capturer/mod.rs b/jinja-lsp-queries/src/capturer/mod.rs deleted file mode 100644 index a3a00aa..0000000 --- a/jinja-lsp-queries/src/capturer/mod.rs +++ /dev/null @@ -1,26 +0,0 @@ -pub mod included; -pub mod init; -pub mod object; -pub mod rust; - -use tree_sitter::{Point, QueryCapture}; - -pub trait Capturer { - fn save_by(&mut self, capture: &QueryCapture<'_>, capture_names: &[String], source: &str); - - fn value(&self, capture: &QueryCapture<'_>, source: &str) -> String { - let value = if let Ok(capture_value) = capture.node.utf8_text(source.as_bytes()) { - capture_value.to_owned() - } else { - "".to_owned() - }; - value - } -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct CaptureDetails { - pub start_position: Point, - pub end_position: Point, - pub value: String, -} diff --git a/jinja-lsp-queries/src/capturer/rust.rs b/jinja-lsp-queries/src/capturer/rust.rs deleted file mode 100644 index 66c510f..0000000 --- a/jinja-lsp-queries/src/capturer/rust.rs +++ /dev/null @@ -1,100 +0,0 @@ -use std::collections::HashMap; -use tree_sitter::Node; - -use tree_sitter::{Point, QueryCapture}; - -use super::Capturer; - -#[derive(Default, Debug, Clone)] -pub struct RustVariables { - variables: HashMap, -} - -impl RustVariables { - pub fn variables(&self) -> &HashMap { - &self.variables - } -} - -#[derive(Default, Debug, Clone)] -pub struct RustCapturer { - macros: HashMap, - variables: Vec<(String, (Point, Point))>, -} - -impl RustCapturer { - pub fn add_macro(&mut self, capture: &QueryCapture<'_>, source: &str) { - let id = capture.node.id(); - if self.macros.get(&id).is_none() { - let mut context_macro = RustVariables::default(); - let mut walker = capture.node.walk(); - let children = capture.node.children(&mut walker); - let mut current = 0; - for child in children { - match child.kind_id() { - 1 => current = 1, - 55 => current = 2, - 152 => { - if current == 2 { - self.check_token_tree(child, &mut context_macro, source); - self.macros.insert(id, context_macro); - break; - } - } - _ => (), - } - } - } - } - - pub fn check_token_tree( - &mut self, - node: Node<'_>, - context_macro: &mut RustVariables, - source: &str, - ) { - let mut walker = node.walk(); - let children = node.children(&mut walker); - for child in children { - if child.kind_id() == 1 { - let text = child.utf8_text(source.as_bytes()); - if let Ok(id) = text { - if context_macro.variables.get(id).is_none() { - let start = child.start_position(); - let end = child.end_position(); - context_macro.variables.insert(id.to_string(), (start, end)); - } - } - } - } - } - - pub fn macros(&self) -> &HashMap { - &self.macros - } - - pub fn variables(&self) -> &Vec<(String, (Point, Point))> { - &self.variables - } - - fn add_name(&mut self, capture: &QueryCapture<'_>, source: &str) { - if let Ok(id) = capture.node.utf8_text(source.as_bytes()) { - let start = capture.node.start_position(); - let end = capture.node.end_position(); - let id = id.to_string(); - let id = id.replace('"', ""); - self.variables.push((id.to_string(), (start, end))); - } - } -} - -impl Capturer for RustCapturer { - fn save_by(&mut self, capture: &QueryCapture<'_>, capture_names: &[String], source: &str) { - let key = capture_names[capture.index as usize].to_owned(); - if key == "context_macro" { - self.add_macro(capture, source); - } else if key == "name" { - self.add_name(capture, source); - } - } -} diff --git a/jinja-lsp-queries/src/lib.rs b/jinja-lsp-queries/src/lib.rs index 02fbfa3..db0bb54 100644 --- a/jinja-lsp-queries/src/lib.rs +++ b/jinja-lsp-queries/src/lib.rs @@ -1,8 +1,5 @@ -pub mod capturer; pub mod lsp_helper; pub mod parsers; -pub mod queries; -pub mod test_queries; +pub mod search; pub mod to_input_edit; pub mod tree_builder; -pub mod types; diff --git a/jinja-lsp-queries/src/lsp_helper.rs b/jinja-lsp-queries/src/lsp_helper.rs index 0472018..0d54f7a 100644 --- a/jinja-lsp-queries/src/lsp_helper.rs +++ b/jinja-lsp-queries/src/lsp_helper.rs @@ -1,83 +1,147 @@ use std::{collections::HashMap, io::ErrorKind}; -use tree_sitter::{Node, Point}; +use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range}; +use tree_sitter::{Point, Tree}; use crate::{ - capturer::object::JinjaObjectCapturer, - queries::{query_props, Queries}, - tree_builder::{DataType, JinjaDiagnostic, JinjaVariable}, + search::{ + objects::objects_query, queries::Queries, templates::templates_query, Identifier, + IdentifierType, + }, + tree_builder::{JinjaDiagnostic, LangType}, }; pub fn search_errors( - root: Node<'_>, + root: &Tree, source: &str, - query: &Queries, - variables: &HashMap>, + queries: &Queries, + variables: &HashMap>, file_name: &String, templates: &String, -) -> Option> { - let trigger_point = Point::new(0, 0); - let query = &query.jinja_idents; - let capturer = JinjaObjectCapturer::default(); - let props = query_props(root, source, trigger_point, query, true, capturer); - let props = props.show(); - let mut diags = vec![]; - for object in props { - if object.is_filter { - continue; - } - let jinja_variables = variables.get(file_name)?; - let mut exist = false; - let mut err_type = JinjaDiagnostic::Undefined; - let mut to_warn = false; - // variable definition is in this file - let located = jinja_variables - .iter() - .filter(|variable| variable.name == object.name) - .filter(|variable| { - exist = true; - object.location.0 >= variable.location.0 - }); - let empty = located.count() == 0; - if empty && exist { - to_warn = true; - } else if empty { - to_warn = true; - for i in variables { - let temp = i.1.iter().filter(|variable| variable.name == object.name); - - if temp.count() != 0 { - err_type = JinjaDiagnostic::DefinedSomewhere; + lang_type: LangType, +) -> Option> { + let mut diagnostics = vec![]; + match lang_type { + LangType::Template => { + let trigger_point = Point::new(0, 0); + let query = &queries.jinja_objects; + let objects = objects_query(query, root, trigger_point, source, true); + let objects = objects.show(); + let this_file = variables.get(file_name)?; + for object in objects { + if object.is_filter { + continue; + } + let mut exist = false; + let mut err_type = JinjaDiagnostic::Undefined; + let mut to_warn = false; + let located = this_file + .iter() + .filter(|variable| { + variable.name == object.name + && variable.identifier_type != IdentifierType::TemplateBlock + }) + .filter(|variable| { + exist = true; + let bigger = object.location.1 >= variable.start; + let global = variable.scope_ends.1 == Point::default(); + let in_scope = object.location.0 < variable.scope_ends.1; + if bigger && global { + true + } else { + bigger && in_scope + } + }); + let empty = located.count() == 0; + if empty && exist { to_warn = true; - break; + } else if empty { + to_warn = true; + for file in variables { + let temp = file + .1 + .iter() + .filter(|variable| variable.name == object.name); + if temp.count() != 0 { + err_type = JinjaDiagnostic::DefinedSomewhere; + to_warn = true; + break; + } + } + } + if to_warn { + let diagnostic = create_diagnostic( + &Identifier::from(&object), + err_type.severity(), + err_type.to_string(), + ); + diagnostics.push(diagnostic); } } + + let mut variables = vec![]; + let query_templates = &queries.jinja_imports; + let jinja_imports = templates_query(query_templates, root, trigger_point, source, true); + jinja_imports.collect(&mut variables); + + let id_templates = variables + .iter() + .filter(|identifier| identifier.identifier_type == IdentifierType::JinjaTemplate); + for i in id_templates { + let err_type = JinjaDiagnostic::TemplateNotFound; + if i.name.is_empty() { + let diagnostic = + create_diagnostic(i, err_type.severity(), err_type.to_string()); + diagnostics.push(diagnostic); + } else { + let path = format!("{templates}/{}", i.name); + if let Err(err) = std::fs::canonicalize(path) { + if err.kind() == ErrorKind::NotFound { + let diagnostic = + create_diagnostic(i, err_type.severity(), err_type.to_string()); + diagnostics.push(diagnostic); + } + } + } + } + Some(diagnostics) } - if to_warn { - let variable = JinjaVariable::new(&object.name, object.location, DataType::Variable); - diags.push((variable, err_type)); - } - } - let jinja_variables = variables.get(file_name)?; - let abc = jinja_variables - .iter() - .filter(|variable| variable.data_type == DataType::Template); - for i in abc { - if i.name.is_empty() { - diags.push((i.clone(), JinjaDiagnostic::TemplateNotFound)); - } else { - let path = format!("{templates}/{}", i.name); - if let Err(err) = std::fs::canonicalize(path) { - if err.kind() == ErrorKind::NotFound { - diags.push((i.clone(), JinjaDiagnostic::TemplateNotFound)); + LangType::Backend => { + let all_variables = variables.get(file_name)?; + let templates2 = all_variables + .iter() + .filter(|id| id.identifier_type == IdentifierType::JinjaTemplate); + for template in templates2 { + let path = format!("{templates}/{}", template.name); + if let Err(err) = std::fs::canonicalize(path) { + if err.kind() == ErrorKind::NotFound { + let diagnostic = create_diagnostic( + template, + DiagnosticSeverity::WARNING, + "Template not found".to_string(), + ); + diagnostics.push(diagnostic); + } } } + Some(diagnostics) } } +} - if diags.is_empty() { - None - } else { - Some(diags) +pub fn create_diagnostic( + template: &Identifier, + severity: DiagnosticSeverity, + message: String, +) -> Diagnostic { + Diagnostic { + range: Range::new( + Position::new(template.start.row as u32, template.start.column as u32), + Position::new(template.end.row as u32, template.end.column as u32), + ), + severity: Some(severity), + message, + source: Some(String::from("jinja-lsp")), + ..Default::default() } } diff --git a/jinja-lsp-queries/src/parsers.rs b/jinja-lsp-queries/src/parsers.rs index 5579bc0..9748a4c 100644 --- a/jinja-lsp-queries/src/parsers.rs +++ b/jinja-lsp-queries/src/parsers.rs @@ -12,11 +12,11 @@ impl Parsers { &mut self, lang_type: LangType, text: &str, - _old_tree: Option<&Tree>, + old_tree: Option<&Tree>, ) -> Option { match lang_type { - LangType::Template => self.jinja.parse(text, None), - LangType::Backend => self.backend.parse(text, None), + LangType::Template => self.jinja.parse(text, old_tree), + LangType::Backend => self.backend.parse(text, old_tree), } } } diff --git a/jinja-lsp-queries/src/queries.rs b/jinja-lsp-queries/src/queries.rs deleted file mode 100644 index 8ab69d0..0000000 --- a/jinja-lsp-queries/src/queries.rs +++ /dev/null @@ -1,146 +0,0 @@ -use tree_sitter::{Node, Point, Query, QueryCursor}; - -use crate::capturer::Capturer; - -#[derive(Debug)] -pub struct Queries { - pub jinja_init: Query, - pub jinja_idents: Query, - pub jinja_imports: Query, - pub rust_idents: Query, -} - -impl Clone for Queries { - fn clone(&self) -> Self { - Self::default() - } -} - -impl Default for Queries { - fn default() -> Self { - Self { - jinja_init: Query::new(tree_sitter_jinja2::language(), INIT).unwrap(), - jinja_idents: Query::new(tree_sitter_jinja2::language(), OBJECTS).unwrap(), - rust_idents: Query::new(tree_sitter_rust::language(), RUST).unwrap(), - jinja_imports: Query::new(tree_sitter_jinja2::language(), IMPORTS).unwrap(), - } - } -} - -pub fn query_props( - node: Node<'_>, - source: &str, - trigger_point: Point, - query: &Query, - all: bool, - mut capturer: T, -) -> T { - let mut cursor_qry = QueryCursor::new(); - let capture_names = query.capture_names(); - let matches = cursor_qry.matches(query, node, source.as_bytes()); - matches - .into_iter() - .flat_map(|m| { - m.captures - .iter() - .filter(|capture| all || capture.node.start_position() <= trigger_point) - }) - .for_each(|capture| { - capturer.save_by(capture, capture_names, source); - }); - capturer -} - -pub static INIT: &str = r#" -( - [ - - (statement - (statement_begin) - (keyword) - (identifier)? @variable - (#not-match? @variable "^(\\d)") - _ - ) @start_statement - - (statement - (statement_begin) - (keyword) @end_keyword - (#match? @end_keyword "^end") - (statement_end) - ) @end_statement - ] -) -"#; - -pub static OBJECTS: &str = r#" -( - [ - ( - (operator) @dot - (#eq? @dot "\.") - ) - - ( - (identifier) @just_id - (#not-match? @just_id "(^\\d+$)") - ) - - ( - (operator) @pipe - (#match? @pipe "\\|") - ) - - (expression) @expr - - ] -) -"#; - -pub static RUST: &str = r#" -([ - - (macro_invocation - (identifier) @context - (token_tree - (identifier) @key_id - ) - (#eq? @context "context") - ) @context_macro - - ( - (field_expression - (identifier) @jinja - (field_identifier) @method - ) - (arguments - (string_literal) @name - ) - - (#eq? @jinja "jinja") - (#match? @method "^(add_global|add_filter|add_function)$") - - ) @function -]) -"#; - -pub static IMPORTS: &str = r#" -[ - (statement - (statement_begin) - (keyword) @keyword - (string)? @template - (ERROR)? @error - (identifier)? @id - (statement_end)? @end - (#match? @keyword "^(include|from)$") - ) - (statement_begin) - (keyword) @keyword - (string)? @template - (ERROR)? @error - (identifier)? @id - (statement_end)? @end - (#match? @keyword "^(include|from)$") -] -"#; diff --git a/jinja-lsp-queries/src/search/definition.rs b/jinja-lsp-queries/src/search/definition.rs new file mode 100644 index 0000000..cb9ff7e --- /dev/null +++ b/jinja-lsp-queries/src/search/definition.rs @@ -0,0 +1,541 @@ +use std::collections::{HashSet, LinkedList}; + +use tree_sitter::{Point, Query, QueryCapture, QueryCursor, Tree}; + +use super::{Identifier, IdentifierType}; + +#[derive(Debug, Clone)] +pub enum Definition { + ForLoop { + key: Identifier, + value: Option, + }, + Set { + key: Identifier, + equals: bool, + }, + With { + keys: Vec, + }, + Macro { + keys: Vec, + scope: usize, + }, + Block { + name: Identifier, + }, +} + +impl Definition { + fn collect(self, ids: &mut Vec) { + match self { + Definition::ForLoop { key, value, .. } => { + ids.push(key); + if let Some(value) = value { + ids.push(value); + } + } + Definition::Set { key, .. } => { + ids.push(key); + } + Definition::With { keys, .. } => { + for key in keys { + ids.push(key); + } + } + Definition::Macro { keys, .. } => { + for key in keys { + ids.push(key); + } + } + Definition::Block { name, .. } => { + ids.push(name); + } + } + } +} + +#[derive(Default, Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub enum Current { + For, + Set, + With, + Macro, + Block, + If, + Else, + Filter, + Autoescape, + Raw, + #[default] + NoDefinition, +} + +impl Current { + fn _from_end(name: &str) -> Self { + match name { + "endfor" => Self::For, + "endset" => Self::Set, + "endwith" => Self::With, + "endmacro" => Self::Macro, + "endblock" => Self::Block, + "endif" => Self::If, + "endelse" => Self::Else, + "endfilter" => Self::Filter, + "endautoescape" => Self::Autoescape, + "endraw" => Self::Raw, + _ => Self::NoDefinition, + } + } + + fn from_rest(name: &str) -> Self { + match name { + "for" => Current::For, + "set" => Current::Set, + "with" => Current::With, + "macro" => Current::Macro, + "block" => Current::Block, + "if" | "elseif" => Current::If, + "else" => Current::Else, + "filter" => Current::Filter, + "autoescape" => Current::Autoescape, + "raw" => Current::Raw, + _ => Current::NoDefinition, + } + } +} + +#[derive(Default, Debug)] +pub struct Scope { + id: usize, + start: Point, + end: Point, +} + +impl Scope { + pub fn new(end: Point) -> Self { + Self { + id: 0, + start: Point::default(), + end, + } + } +} + +#[derive(Default, Debug)] +pub struct JinjaDefinitions { + pub definitions: Vec, + pub current_definition: Current, + pub current_scope: LinkedList, + pub all_scopes: Vec, + all_ids: HashSet, + pub in_end: bool, +} + +impl JinjaDefinitions { + pub fn exist(&self, id: usize) -> bool { + self.all_ids.iter().any(|item| item == &id) + } + + pub fn add(&mut self, id: usize, current: Current) { + self.current_definition = current; + let mut def = None; + let mut add_scope = false; + match current { + Current::For => { + def = Some(Definition::ForLoop { + key: Identifier::default(), + value: None, + }); + add_scope = true; + } + Current::Set => { + // if !self.all_ids.contains(&id) { + self.all_ids.insert(id); + def = Some(Definition::Set { + key: Identifier::default(), + equals: false, + }); + // } + } + Current::With => { + def = Some(Definition::With { keys: vec![] }); + add_scope = true; + } + Current::Macro => { + def = Some(Definition::Macro { + keys: vec![], + scope: self.current_scope.front().unwrap_or(&Scope::default()).id, + }); + add_scope = true; + } + Current::Block => { + def = Some(Definition::Block { + name: Identifier::default(), + }); + add_scope = true; + } + Current::NoDefinition => (), + _ => { + add_scope = true; + } + } + if let Some(def) = def { + // let same_def = self.definitions.iter().find(|item| item.) + self.definitions.push(def); + self.all_ids.insert(id); + } + if add_scope { + self.current_scope.push_front(Scope { + id, + ..Default::default() + }); + } + } + + fn check(&mut self, name: &str, capture: &QueryCapture<'_>, text: &str) -> Option { + match name { + "error" => { + return None; + } + "for" => { + if !self.exist(capture.node.id()) { + self.add(capture.node.id(), Current::For); + } else { + self.current_definition = Current::NoDefinition; + } + } + + "for_key" => { + if self.current_definition == Current::For { + let def = self.definitions.last_mut()?; + if let Definition::ForLoop { key, .. } = def { + let start = capture.node.start_position(); + let end = capture.node.end_position(); + let content = capture.node.utf8_text(text.as_bytes()).ok()?; + key.name = content.to_string(); + key.start = start; + key.end = end; + key.identifier_type = IdentifierType::ForLoopKey; + key.scope_ends.0 = + self.current_scope.front().unwrap_or(&Scope::default()).id; + } + } + } + + "for_value" => { + if self.current_definition == Current::For { + let def = self.definitions.last_mut()?; + if let Definition::ForLoop { value, .. } = def { + let mut identifier = Identifier::default(); + let start = capture.node.start_position(); + let end = capture.node.end_position(); + let content = capture.node.utf8_text(text.as_bytes()).ok()?; + identifier.name = content.to_owned(); + identifier.start = start; + identifier.end = end; + identifier.identifier_type = IdentifierType::ForLoopValue; + identifier.scope_ends.0 = + self.current_scope.front().unwrap_or(&Scope::default()).id; + *value = Some(identifier); + } + } + } + + "set" => { + if !self.exist(capture.node.id()) { + self.add(capture.node.id(), Current::Set); + } else { + self.current_definition = Current::NoDefinition; + } + } + + "set_identifier" => { + if self.current_definition == Current::Set { + let def = self.definitions.last_mut()?; + if let Definition::Set { key, .. } = def { + let start = capture.node.start_position(); + let end = capture.node.end_position(); + let content = capture.node.utf8_text(text.as_bytes()).ok()?; + if content == key.name { + return None; + } + key.name = content.to_string(); + key.start = start; + key.end = end; + let scope = self.current_scope.front().unwrap_or(&Scope::default()).id; + key.scope_ends.0 = scope; + key.identifier_type = IdentifierType::SetVariable; + } + } + } + + "equals" => { + if self.current_definition == Current::Set { + let def = self.definitions.last_mut()?; + if let Definition::Set { equals, key } = def { + *equals = true; + key.scope_ends.0 = + self.current_scope.front().unwrap_or(&Scope::default()).id; + } + } + } + + "with" => { + if !self.exist(capture.node.id()) { + self.add(capture.node.id(), Current::With); + } else { + // self.current_definition = Current::NoDefinition; + } + } + + "with_identifier" => { + if self.current_definition == Current::With { + let def = self.definitions.last_mut()?; + if let Definition::With { keys, .. } = def { + let start = capture.node.start_position(); + let end = capture.node.end_position(); + let content = capture.node.utf8_text(text.as_bytes()).ok()?; + let mut key = Identifier::new(content, start, end); + key.identifier_type = IdentifierType::WithVariable; + key.scope_ends.0 = + self.current_scope.front().unwrap_or(&Scope::default()).id; + keys.push(key); + } + } + } + "block" => { + if !self.exist(capture.node.id()) { + self.add(capture.node.id(), Current::Block); + } else { + self.current_definition = Current::NoDefinition; + } + } + + "block_identifier" => { + if self.current_definition == Current::Block { + let def = self.definitions.last_mut()?; + if let Definition::Block { name, .. } = def { + let start = capture.node.start_position(); + let end = capture.node.end_position(); + let content = capture.node.utf8_text(text.as_bytes()).ok()?; + name.name = content.to_string(); + name.start = start; + name.end = end; + name.identifier_type = IdentifierType::TemplateBlock; + name.scope_ends.0 = + self.current_scope.front().unwrap_or(&Scope::default()).id; + } + } + } + + "macro" => { + if !self.exist(capture.node.id()) { + self.add(capture.node.id(), Current::Macro); + } else { + self.current_definition = Current::Macro; + // self.current_definition = Current::NoDefinition; + } + } + + "macro_identifier" => { + if self.current_definition == Current::Macro { + let def = self.definitions.last_mut()?; + if let Definition::Macro { keys, .. } = def { + let start = capture.node.start_position(); + let end = capture.node.end_position(); + let content = capture.node.utf8_text(text.as_bytes()).ok()?; + + let mut key = Identifier::new(content, start, end); + key.scope_ends.0 = + self.current_scope.front().unwrap_or(&Scope::default()).id; + if keys.is_empty() { + key.identifier_type = IdentifierType::MacroName; + } else { + key.identifier_type = IdentifierType::MacroParameter; + } + keys.push(key); + } + } + } + + "if" | "elif" | "else" | "filter" | "autoescape" | "raw" => { + let current = Current::from_rest(name); + self.add(capture.node.id(), current); + } + "ended" => { + self.in_end = true; + } + + "range_end" => { + if self.in_end { + self.in_end = false; + let mut last = self.current_scope.pop_front()?; + if last.end == Point::default() { + last.end = capture.node.start_position(); + self.current_definition = Current::NoDefinition; + self.all_scopes.push(last); + } + } + } + + "range_start" => { + let mut can_add = true; + let mut is_set = false; + if let Some(last) = self.current_scope.front_mut() { + if let Some(Definition::Set { equals, .. }) = self.definitions.last() { + is_set = !equals; + if self.current_definition == Current::Set && *equals { + can_add = false; + } + } + if is_set && can_add { + self.current_scope.push_front(Scope { + id: capture.node.id(), + start: capture.node.end_position(), + ..Default::default() + }); + } else if can_add { + last.start = capture.node.end_position(); + } + } else if self.current_definition != Current::NoDefinition { + if let Some(Definition::Set { equals, .. }) = self.definitions.last() { + if self.current_definition == Current::Set && !(*equals) { + can_add = true; + } + } + + if can_add { + self.current_scope.push_front(Scope { + id: capture.node.id(), + start: capture.node.end_position(), + ..Default::default() + }); + } + } + } + + // "range_end" => { + // let last = self.definitions.last()?; + // match last { + // Definition::ForLoop { .. } => { + // self.current = Current::For; + // } + // Definition::Set { .. } => { + // self.current = Current::Set; + // } + // Definition::With { .. } => { + // self.current = Current::With; + // } + // Definition::Macro { .. } => { + // self.current = Current::Macro; + // } + // Definition::Block { .. } => { + // self.current = Current::Block; + // } + // } + // } + _ => {} + } + Some(true) + } + + pub fn identifiers(self) -> Vec { + let mut ids = vec![]; + for id in self.definitions { + id.collect(&mut ids); + } + ids + } + + fn fix_end(&mut self, last_line: Point) { + let global_scope = Scope::new(last_line); + for def in self.definitions.iter_mut() { + match def { + Definition::ForLoop { key, value } => { + let scope = self + .all_scopes + .iter() + .find(|item| item.id == key.scope_ends.0); + let scope = scope.unwrap_or(&global_scope); + key.scope_ends.1 = scope.end; + if let Some(value) = value { + value.scope_ends.1 = scope.end; + } + } + Definition::Set { key, .. } => { + let scope = self + .all_scopes + .iter() + .find(|item| item.id == key.scope_ends.0); + let scope = scope.unwrap_or(&global_scope); + key.scope_ends.1 = scope.end; + } + Definition::With { keys } => { + for key in keys { + let scope = self + .all_scopes + .iter() + .find(|item| item.id == key.scope_ends.0); + let scope = scope.unwrap_or(&global_scope); + key.scope_ends.1 = scope.end; + } + } + Definition::Macro { keys, scope } => { + let scope = self.all_scopes.iter().find(|item| item.id == *scope); + let scope = scope.unwrap_or(&global_scope); + for (index, key) in keys.iter_mut().enumerate() { + if index == 0 { + key.scope_ends.1 = scope.end; + } else { + let scope = self + .all_scopes + .iter() + .find(|item| item.id == key.scope_ends.0); + let scope = scope.unwrap_or(&global_scope); + key.scope_ends.1 = scope.end; + } + } + } + Definition::Block { name } => { + let scope = self + .all_scopes + .iter() + .find(|item| item.id == name.scope_ends.0); + let scope = scope.unwrap_or(&global_scope); + name.scope_ends.1 = scope.end; + } + } + // let id = self.all_scopes.iter().find(|scope| scope.id == ) + } + } +} + +pub fn definition_query( + query: &Query, + tree: &Tree, + trigger_point: Point, + text: &str, + all: bool, +) -> JinjaDefinitions { + let closest_node = tree.root_node(); + let mut definitions = JinjaDefinitions::default(); + let mut cursor_qry = QueryCursor::new(); + let capture_names = query.capture_names(); + let matches = cursor_qry.matches(query, closest_node, text.as_bytes()); + let captures = matches.into_iter().flat_map(|m| { + m.captures + .iter() + .filter(|capture| all || capture.node.start_position() <= trigger_point) + }); + for capture in captures { + let name = &capture_names[capture.index as usize]; + let err = definitions.check(name, capture, text); + if err.is_none() { + break; + } + } + let root = tree.root_node().end_position(); + definitions.fix_end(root); + definitions +} diff --git a/jinja-lsp-queries/src/search/definition2.rs b/jinja-lsp-queries/src/search/definition2.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/jinja-lsp-queries/src/search/definition2.rs @@ -0,0 +1 @@ + diff --git a/jinja-lsp-queries/src/search/jinja_state.rs b/jinja-lsp-queries/src/search/jinja_state.rs new file mode 100644 index 0000000..6f98966 --- /dev/null +++ b/jinja-lsp-queries/src/search/jinja_state.rs @@ -0,0 +1,39 @@ +use tree_sitter::{Point, Query, Tree}; + +use super::{ + definition::{definition_query, JinjaDefinitions}, + objects::{objects_query, JinjaObjects}, + templates::{templates_query, JinjaImports}, +}; + +#[derive(Default)] +pub struct JinjaState { + jinja_definitions: JinjaDefinitions, + jinja_objects: JinjaObjects, + jinja_imports: JinjaImports, +} + +impl JinjaState { + pub fn init( + trigger_point: Point, + query: (&Query, &Query, &Query), + tree: &Tree, + source: &str, + all: bool, + ) -> Self { + let definitions = definition_query(query.0, tree, trigger_point, source, all); + let objects = objects_query(query.1, tree, trigger_point, source, all); + let imports = templates_query(query.2, tree, trigger_point, source, all); + Self { + jinja_definitions: definitions, + jinja_objects: objects, + jinja_imports: imports, + } + } + + pub fn reset(&mut self) { + self.jinja_definitions = Default::default(); + self.jinja_objects = Default::default(); + self.jinja_imports = Default::default(); + } +} diff --git a/jinja-lsp-queries/src/search/mod.rs b/jinja-lsp-queries/src/search/mod.rs new file mode 100644 index 0000000..5c05a27 --- /dev/null +++ b/jinja-lsp-queries/src/search/mod.rs @@ -0,0 +1,127 @@ +use tower_lsp::lsp_types::{CompletionItemKind, Position, Range, SymbolKind}; +use tree_sitter::Point; + +use self::objects::JinjaObject; + +pub mod definition; +pub mod jinja_state; +pub mod objects; +pub mod queries; +pub mod rust_identifiers; +pub mod rust_state; +pub mod rust_template_completion; +pub mod snippets_completion; +pub mod templates; +pub mod test_queries; + +#[derive(Default, Debug, Clone, PartialEq, PartialOrd, Ord, Eq)] +pub struct Identifier { + pub start: Point, + pub end: Point, + pub name: String, + pub scope_ends: (usize, Point), + pub identifier_type: IdentifierType, +} + +impl Identifier { + pub fn new(name: &str, start: Point, end: Point) -> Self { + Self { + name: String::from(name), + start, + end, + scope_ends: (0, Point::default()), + identifier_type: IdentifierType::UndefinedVariable, + } + } +} + +impl From<&JinjaObject> for Identifier { + fn from(value: &JinjaObject) -> Self { + Identifier::new(&value.name, value.location.0, value.location.1) + } +} + +pub fn completion_start(trigger_point: Point, identifier: &Identifier) -> Option<&str> { + let len = identifier.name.len(); + let diff = identifier.end.column - trigger_point.column; + if diff == 0 || diff == 1 { + return Some(""); + } + if diff > len { + return None; + } + let to = len - diff; + let s = identifier.name.get(0..to + 1); + s +} +pub fn to_range(points: (Point, Point)) -> Range { + let start = Position::new(points.0.row as u32, points.0.column as u32); + let end = Position::new(points.1.row as u32, points.1.column as u32); + Range::new(start, end) +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum IdentifierType { + ForLoopKey, + ForLoopValue, + ForLoopCount, + SetVariable, + WithVariable, + MacroName, + MacroParameter, + TemplateBlock, + BackendVariable, + #[default] + UndefinedVariable, + JinjaTemplate, +} + +impl IdentifierType { + pub fn completion_detail(&self) -> &'static str { + match self { + IdentifierType::ForLoopKey => "For loop key", + IdentifierType::ForLoopValue => "For loop value", + IdentifierType::ForLoopCount => "For loop count", + IdentifierType::SetVariable => "Set variable", + IdentifierType::WithVariable => "With variable", + IdentifierType::MacroName => "Macro", + IdentifierType::MacroParameter => "Macro parameter", + IdentifierType::TemplateBlock => "Template block", + IdentifierType::BackendVariable => "Backend variable", + IdentifierType::UndefinedVariable => "Undefined variable", + IdentifierType::JinjaTemplate => "Jinja template", + } + } + + pub fn completion_kind(&self) -> CompletionItemKind { + match self { + IdentifierType::ForLoopKey => CompletionItemKind::VARIABLE, + IdentifierType::ForLoopValue => CompletionItemKind::VARIABLE, + IdentifierType::ForLoopCount => CompletionItemKind::FIELD, + IdentifierType::SetVariable => CompletionItemKind::VARIABLE, + IdentifierType::WithVariable => CompletionItemKind::VARIABLE, + IdentifierType::MacroName => CompletionItemKind::FUNCTION, + IdentifierType::MacroParameter => CompletionItemKind::FIELD, + IdentifierType::TemplateBlock => CompletionItemKind::MODULE, + IdentifierType::BackendVariable => CompletionItemKind::VARIABLE, + IdentifierType::UndefinedVariable => CompletionItemKind::CONSTANT, + IdentifierType::JinjaTemplate => CompletionItemKind::FILE, + } + } + + pub fn symbol_kind(&self) -> SymbolKind { + match self { + IdentifierType::ForLoopKey => SymbolKind::VARIABLE, + IdentifierType::ForLoopValue => SymbolKind::VARIABLE, + IdentifierType::ForLoopCount => SymbolKind::FIELD, + IdentifierType::SetVariable => SymbolKind::VARIABLE, + IdentifierType::WithVariable => SymbolKind::VARIABLE, + IdentifierType::MacroName => SymbolKind::FUNCTION, + IdentifierType::MacroParameter => SymbolKind::FIELD, + IdentifierType::TemplateBlock => SymbolKind::MODULE, + IdentifierType::BackendVariable => SymbolKind::VARIABLE, + IdentifierType::UndefinedVariable => SymbolKind::CONSTANT, + IdentifierType::JinjaTemplate => SymbolKind::FILE, + } + } +} diff --git a/jinja-lsp-queries/src/capturer/object.rs b/jinja-lsp-queries/src/search/objects.rs similarity index 50% rename from jinja-lsp-queries/src/capturer/object.rs rename to jinja-lsp-queries/src/search/objects.rs index 4f167b6..bd479d2 100644 --- a/jinja-lsp-queries/src/capturer/object.rs +++ b/jinja-lsp-queries/src/search/objects.rs @@ -1,10 +1,31 @@ use tower_lsp::lsp_types::Range; -use tree_sitter::{Point, QueryCapture}; +use tree_sitter::{Point, Query, QueryCapture, QueryCursor, Tree}; -use super::Capturer; +#[derive(Default, Debug, Clone)] +pub struct JinjaObject { + pub name: String, + pub location: (Point, Point), + pub is_filter: bool, + fields: Vec<(String, (Point, Point))>, +} + +impl JinjaObject { + pub fn new(name: String, start: Point, end: Point, is_filter: bool) -> Self { + Self { + name, + location: (start, end), + fields: vec![], + is_filter, + } + } + + pub fn add_field(&mut self, field: String, start: Point, end: Point) { + self.fields.push((field, (start, end))); + } +} #[derive(Default, Debug)] -pub struct JinjaObjectCapturer { +pub struct JinjaObjects { objects: Vec, dot: (Point, Point), pipe: (Point, Point), @@ -12,52 +33,32 @@ pub struct JinjaObjectCapturer { ident: (Point, Point), } -impl JinjaObjectCapturer { - pub fn show(&self) -> Vec { - self.objects.clone() - } - - fn add_operator(&mut self, capture: &QueryCapture<'_>, dot: u8) { +impl JinjaObjects { + fn check(&mut self, name: &str, capture: &QueryCapture<'_>, source: &str) -> Option<()> { let start = capture.node.start_position(); let end = capture.node.end_position(); - if dot == 0 { - self.dot = (start, end); - } else if dot == 1 { - self.pipe = (start, end); - } else if dot == 2 { - self.expr = (start, end); - } - } - - pub fn in_pipe(&self, trigger_point: Point) -> bool { - trigger_point >= self.pipe.0 && trigger_point <= self.pipe.1 - } - - pub fn in_expr(&self, trigger_point: Point) -> bool { - trigger_point >= self.expr.0 && trigger_point <= self.expr.1 && trigger_point > self.ident.1 - } - - pub fn is_hover(&self, trigger_point: Point) -> bool { - trigger_point >= self.ident.0 - && trigger_point <= self.ident.1 - && self.pipe.1 == self.ident.0 - } - - pub fn is_ident(&self, trigger_point: Point) -> Option { - if trigger_point >= self.ident.0 && trigger_point <= self.ident.1 { - self.objects.last().map(|last| last.name.to_string()) - } else { - None - } - } - - pub fn completion(&self, trigger_point: Point) -> Option { - if self.in_pipe(trigger_point) { - return Some(CompletionType::Filter); - } else if self.in_expr(trigger_point) { - return Some(CompletionType::Identifier); + match name { + "error" => { + return None; + } + "just_id" => { + self.build_object(capture, source); + } + "dot" => { + self.dot = (start, end); + } + "pipe" => { + let content = capture.node.utf8_text(source.as_bytes()).ok()?; + if content.starts_with('|') { + self.pipe = (start, end); + } + } + "expr" => { + self.expr = (start, end); + } + _ => (), } - None + Some(()) } pub fn build_object(&mut self, capture: &QueryCapture<'_>, source: &str) { @@ -66,18 +67,26 @@ impl JinjaObjectCapturer { let end = capture.node.end_position(); if let Ok(value) = value { if start.row == self.dot.1.row && start.column == self.dot.1.column { - if let Some(last) = self.objects.last_mut() { + let last_object = self.objects.last_mut().map(|last| { last.fields.push((String::from(value), (start, end))); self.ident = (start, end); - } else { - // TODO: in future add those to main library - if VALID_IDENTIFIERS.contains(&value) { - return; + }); + match last_object { + Some(_) => {} + None => { + // TODO: in future add those to main library + if VALID_IDENTIFIERS.contains(&value) { + return; + } + self.ident = (start, end); + let is_filter = self.is_hover(start) && self.is_filter(); + self.objects.push(JinjaObject::new( + String::from(value), + start, + end, + is_filter, + )); } - self.ident = (start, end); - let is_filter = self.is_hover(start); - self.objects - .push(JinjaObject::new(String::from(value), start, end, is_filter)); } } else { // TODO: in future add those to main library @@ -85,54 +94,80 @@ impl JinjaObjectCapturer { return; } self.ident = (start, end); - let is_filter = self.is_hover(start); + let is_filter = self.is_hover(start) && self.is_filter(); self.objects .push(JinjaObject::new(String::from(value), start, end, is_filter)); } } } - pub fn get_last_id(&self) -> Option { - self.objects.last().map(|last| last.name.to_string()) + pub fn completion(&self, trigger_point: Point) -> Option { + if self.in_pipe(trigger_point) { + return Some(CompletionType::Filter); + } else if self.in_expr(trigger_point) { + return Some(CompletionType::Identifier); + } + None } -} -impl Capturer for JinjaObjectCapturer { - fn save_by(&mut self, capture: &QueryCapture<'_>, capture_names: &[String], source: &str) { - let key = capture_names[capture.index as usize].to_owned(); - if key == "just_id" { - self.build_object(capture, source); - } else if key == "dot" { - self.add_operator(capture, 0); - } else if key == "pipe" { - self.add_operator(capture, 1); - } else if key == "expr" { - self.add_operator(capture, 2); - } + pub fn in_pipe(&self, trigger_point: Point) -> bool { + trigger_point >= self.pipe.0 && trigger_point <= self.pipe.1 } -} -#[derive(Default, Debug, Clone)] -pub struct JinjaObject { - pub location: (Point, Point), - pub name: String, - pub fields: Vec<(String, (Point, Point))>, - pub is_filter: bool, -} + pub fn in_expr(&self, trigger_point: Point) -> bool { + trigger_point >= self.expr.0 && trigger_point <= self.expr.1 && trigger_point > self.ident.1 + } -impl JinjaObject { - pub fn new(name: String, start: Point, end: Point, is_filter: bool) -> Self { - Self { - name, - location: (start, end), - fields: vec![], - is_filter, + pub fn is_ident(&self, trigger_point: Point) -> Option { + if trigger_point >= self.ident.0 && trigger_point <= self.ident.1 { + self.objects.last().map(|last| last.name.to_string()) + } else { + None } } - pub fn add_field(&mut self, field: String, start: Point, end: Point) { - self.fields.push((field, (start, end))); + pub fn is_hover(&self, trigger_point: Point) -> bool { + trigger_point >= self.ident.0 && trigger_point <= self.ident.1 + } + + pub fn is_filter(&self) -> bool { + self.pipe.1 == self.ident.0 + } + + pub fn get_last_id(&self) -> Option<&JinjaObject> { + self.objects.last() + } + + pub fn show(&self) -> Vec { + self.objects.clone() + } +} + +pub fn objects_query( + query: &Query, + tree: &Tree, + trigger_point: Point, + text: &str, + all: bool, +) -> JinjaObjects { + let closest_node = tree.root_node(); + let mut objects = JinjaObjects::default(); + let mut cursor_qry = QueryCursor::new(); + let capture_names = query.capture_names(); + let matches = cursor_qry.matches(query, closest_node, text.as_bytes()); + let captures = matches.into_iter().flat_map(|m| { + m.captures + .iter() + .filter(|capture| all || capture.node.start_position() <= trigger_point) + }); + for capture in captures { + let name = &capture_names[capture.index as usize]; + let checked = objects.check(name, capture, text); + if checked.is_none() { + break; + } } + objects } #[derive(PartialEq, Debug)] @@ -140,6 +175,7 @@ pub enum CompletionType { Filter, Identifier, IncludedTemplate { name: String, range: Range }, + Snippets { range: Range }, } -static VALID_IDENTIFIERS: [&str; 4] = ["loop", "true", "false", "not"]; +static VALID_IDENTIFIERS: [&str; 6] = ["loop", "true", "false", "not", "as", "module"]; diff --git a/jinja-lsp-queries/src/search/queries.rs b/jinja-lsp-queries/src/search/queries.rs new file mode 100644 index 0000000..2668103 --- /dev/null +++ b/jinja-lsp-queries/src/search/queries.rs @@ -0,0 +1,304 @@ +use tree_sitter::Query; + +#[derive(Debug)] +pub struct Queries { + pub jinja_definitions: Query, + pub jinja_objects: Query, + pub jinja_imports: Query, + pub rust_definitions: Query, + pub rust_templates: Query, + pub jinja_snippets: Query, +} + +impl Clone for Queries { + fn clone(&self) -> Self { + Self::default() + } +} + +impl Default for Queries { + fn default() -> Self { + Self { + jinja_definitions: Query::new(tree_sitter_jinja2::language(), DEFINITIONS).unwrap(), + jinja_objects: Query::new(tree_sitter_jinja2::language(), OBJECTS).unwrap(), + rust_definitions: Query::new(tree_sitter_rust::language(), RUST_DEFINITIONS).unwrap(), + jinja_imports: Query::new(tree_sitter_jinja2::language(), JINJA_IMPORTS).unwrap(), + rust_templates: Query::new(tree_sitter_rust::language(), RUST_TEMPLATES).unwrap(), + jinja_snippets: Query::new(tree_sitter_jinja2::language(), JINJA_SNIPPETS).unwrap(), + } + } +} + +const DEFINITIONS: &str = r#" +( + [ + (statement + (statement_begin) + (keyword) @for_keyword + [ + ( + (operator)? @open_par + (identifier)? @for_key + . + (operator)? @comma + . + (identifier)? @for_value + . + (operator)? @close_par + (_). + ) @for2 + + ( + (identifier) @for_key + ) @for1 + ] + + + (#eq? @open_par "\(") + (#match-eq? @comma ",") + (#eq? @close_par "\)") + (#not-match? @for_key "(^\\d+$)") + (#not-match? @for_value "(^\\d+$)") + + (keyword) @in + (#eq @in "in") + (#eq? @for_keyword "for") + (identifier) @for_items + (_)? @other + (statement_end) @range_start + ) @for + + ( + (statement + (statement_begin) + (keyword) @set_keyword + (identifier) @set_identifier + (operator)? @equals + (_)? @others + (statement_end) @range_start + + (#eq? @set_keyword "set") + (#not-match? @set_identifier "(^\\d+$)") + (#eq? @equals "= ") + ) + ) @set + + (statement + (statement_begin) + (keyword) @with_keyword + (identifier) @with_identifier + (#eq? @with_keyword "with") + (#not-match? @with_identifier "(^\\d+$)") + (statement_end) @range_start + ) @with + + (statement + (statement_begin) + (keyword) @macro_keyword + (identifier) @macro_identifier + (#eq? @macro_keyword "macro") + (#not-match? @macro_identifier "(^\\d+$)") + (statement_end) @range_start + ) @macro + + (statement + (statement_begin) + (keyword) @block_keyword + (identifier) @block_identifier + (#eq? @block_keyword "block") + (#not-match? @block_identifier "(^\\d+$)") + (statement_end) @range_start + ) @block + + + (statement + (statement_begin) + (keyword) @ifkeyword + (#eq? @ifkeyword "if") + (statement_end) @range_start + ) @if + + (statement + (statement_begin) + (keyword) @elifkeyword + (#eq? @elifkeyword "elif") + (statement_end) @range_start + ) @elif + + (statement + (statement_begin) + (keyword) @elsekeyword + (#eq? @elsekeyword "else") + (statement_end) @range_start + ) @else + + + (statement + (statement_begin) + (keyword) @filterkeyword + (#eq? @filterkeyword "filter") + (statement_end) @range_start + ) @filter + + (statement + (statement_begin) + (keyword) @autokeyword + (#eq? @autokeyword "autoescape") + (statement_end) @range_start + ) @autoescape + + (statement + (statement_begin) + (keyword) @rawkeyword + (#eq? @rawkeyword "raw") + (statement_end) @range_start + ) @raw + (ERROR) @error + ] +) +[ + (statement + (statement_begin) @range_end + (keyword) @endkeyword + (#match? @endkeyword "^end") + (statement_end) + ) @ended + +] + +"#; + +const OBJECTS: &str = r#" + ( + [ + ( + (operator) @dot + (#eq? @dot "\.") + ) + + ( + (identifier) @just_id + (#not-match? @just_id "(^\\d+$)") + ) + + ( + (operator) @pipe + ) + + (expression) @expr + + (ERROR) @error + + ] +) +"#; + +pub static RUST_DEFINITIONS: &str = r#" +([ + (macro_invocation + (identifier) @context + (#eq? @context "context") + ) @macro + + (token_tree + (identifier) @key_id + (#not-eq? @key_id "context") + ) + + ( + (field_expression + (identifier) @jinja + (field_identifier) @method + ) + (arguments + (string_literal)+ @name + ) + + (#eq? @jinja "jinja") + (#match? @method "(add_global|add_filter|add_function)") + + ) @function +]) +"#; + +const JINJA_IMPORTS: &str = r#" + +( + [ + + (statement + (statement_begin) + (keyword) @extends_keyword + (string) @template_name + (statement_end) + (#eq? @extends_keyword "extends") + ) @extends + + + (statement + (statement_begin) + (keyword) @include_keyword + (string) @template_name + (statement_end) + (#eq? @include_keyword "include") + ) @include + + (statement + (statement_begin) + (keyword) @from_keyword + (string) @template_name + (keyword)? @import_keyword + (identifier)? @import_identifier + (#not-match? @import_identifier "(^\\d)") + (statement_end) + (#eq? @from_keyword "from") + (#eq? @import_keyword "import") + ) @from + + + (statement + (statement_begin) + (keyword) @import_keyword + (string) @template_name + (identifier)? @as_keyword + (identifier)? @import_identifier + (#not-match? @import_identifier "(^\\d)") + (#eq? @import_keyword "import") + (#eq? @as_keyword "as") + (statement_end) + ) @import + + (ERROR) @error + ] +) +"#; + +const RUST_TEMPLATES: &str = r#" +(call_expression + [ + (field_expression + (field_identifier) @method_name + ) + (identifier) @method_name + (#any-of? @method_name "render_jinja" "get_template") + ;;(#match? @method_name "(render_jinja|get_template)") + ] + (arguments + (string_literal)+ @template_name + ) +) +"#; + +const JINJA_SNIPPETS: &str = r#" +[ + (statement_begin) @start + (statement_end) @end + (ERROR + (ERROR) @error + ) @error_block + + ( + (keyword) @keyword + ) +] +"#; diff --git a/jinja-lsp-queries/src/search/rust_identifiers.rs b/jinja-lsp-queries/src/search/rust_identifiers.rs new file mode 100644 index 0000000..a68d825 --- /dev/null +++ b/jinja-lsp-queries/src/search/rust_identifiers.rs @@ -0,0 +1,80 @@ +use tree_sitter::{Point, Query, QueryCapture, QueryCursor, Tree}; + +use super::{Identifier, IdentifierType}; + +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub enum Current { + InMacro(Point), + #[default] + Free, +} + +#[derive(Default, Debug, Clone)] +pub struct RustIdentifiers { + variables: Vec, + current: Current, +} + +impl RustIdentifiers { + pub fn show(self) -> Vec { + self.variables + } + + pub fn check(&mut self, name: &str, capture: &QueryCapture<'_>, text: &str) -> Option<()> { + match name { + "macro" => { + let end = capture.node.end_position(); + self.current = Current::InMacro(end); + } + "key_id" => { + if let Current::InMacro(end_macro) = self.current { + let start = capture.node.start_position(); + let end = capture.node.end_position(); + if start > end_macro { + self.current = Current::Free; + return None; + } + let name = capture.node.utf8_text(text.as_bytes()).ok()?; + let mut identifier = Identifier::new(name, start, end); + identifier.identifier_type = IdentifierType::BackendVariable; + self.variables.push(identifier); + } + } + "name" => { + let start = capture.node.start_position(); + let end = capture.node.end_position(); + let name = capture.node.utf8_text(text.as_bytes()).ok()?; + let name = name.replace(['\"', '\''], ""); + let mut identifier = Identifier::new(&name, start, end); + identifier.identifier_type = IdentifierType::BackendVariable; + self.variables.push(identifier); + } + _ => (), + } + None + } +} + +pub fn rust_definition_query( + query: &Query, + tree: &Tree, + trigger_point: Point, + text: &str, + all: bool, +) -> RustIdentifiers { + let closest_node = tree.root_node(); + let mut cursor_qry = QueryCursor::new(); + let mut rust = RustIdentifiers::default(); + let capture_names = query.capture_names(); + let matches = cursor_qry.matches(query, closest_node, text.as_bytes()); + let captures = matches.into_iter().flat_map(|m| { + m.captures + .iter() + .filter(|capture| all || capture.node.start_position() <= trigger_point) + }); + for capture in captures { + let name = &capture_names[capture.index as usize]; + rust.check(name, capture, text); + } + rust +} diff --git a/jinja-lsp-queries/src/search/rust_state.rs b/jinja-lsp-queries/src/search/rust_state.rs new file mode 100644 index 0000000..7232dc0 --- /dev/null +++ b/jinja-lsp-queries/src/search/rust_state.rs @@ -0,0 +1,70 @@ +use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range, Url}; +use tree_sitter::{Point, Query, Tree}; + +use crate::search::{ + rust_identifiers::rust_definition_query, rust_template_completion::rust_templates_query, +}; + +use super::{rust_identifiers::RustIdentifiers, rust_template_completion::RustTemplates}; + +#[derive(Default)] +pub struct RustState { + rust_identifiers: RustIdentifiers, + rust_templates: RustTemplates, +} + +impl RustState { + pub fn init( + trigger_point: Point, + query: (&Query, &Query), + tree: &Tree, + source: &str, + all: bool, + ) -> Self { + let ids = rust_definition_query(query.0, tree, trigger_point, source, all); + let templates = rust_templates_query(query.1, tree, trigger_point, source, all); + RustState { + rust_identifiers: ids, + rust_templates: templates, + } + } + + pub fn reset(&mut self) { + self.rust_identifiers = RustIdentifiers::default(); + self.rust_templates = RustTemplates::default(); + } + + pub fn template_errors(&self, root: &str) -> Option> { + let mut diagnostics = vec![]; + for id in &self.rust_templates.templates { + let name = &id.name; + let template = format!("{}/{}", root, name); + let template = std::fs::canonicalize(template); + let mut is_error = false; + if template.is_err() { + is_error = true; + } else { + let buffer = template.ok()?; + let url = format!("file://{}", buffer.to_str()?); + let url = Url::parse(&url).ok(); + if url.is_none() { + is_error = true; + } + } + if is_error { + let diagnostic = Diagnostic { + range: Range::new( + Position::new(id.start.row as u32, id.start.column as u32), + Position::new(id.end.row as u32, id.end.column as u32), + ), + severity: Some(DiagnosticSeverity::WARNING), + message: "Template not found".to_owned(), + source: Some(String::from("jinja-lsp")), + ..Default::default() + }; + diagnostics.push(diagnostic); + } + } + Some(diagnostics) + } +} diff --git a/jinja-lsp-queries/src/search/rust_template_completion.rs b/jinja-lsp-queries/src/search/rust_template_completion.rs new file mode 100644 index 0000000..41e4d78 --- /dev/null +++ b/jinja-lsp-queries/src/search/rust_template_completion.rs @@ -0,0 +1,75 @@ +use tree_sitter::{Point, Query, QueryCapture, QueryCursor, Tree}; + +use super::{Identifier, IdentifierType}; + +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct RustTemplateCompletion { + pub template_name: Identifier, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct RustTemplates { + pub templates: Vec, + in_method: bool, +} + +impl RustTemplates { + pub fn in_template(&self, trigger_point: Point) -> Option<&Identifier> { + let last = self.templates.last()?; + if trigger_point >= last.start && trigger_point <= last.end { + Some(last) + } else { + None + } + } + + pub fn check(&mut self, name: &str, capture: &QueryCapture<'_>, text: &str) -> Option<()> { + if name == "template_name" && self.in_method { + let template = capture.node.utf8_text(text.as_bytes()).ok()?; + let template = template.replace(['\"', '\''], ""); + let start = capture.node.start_position(); + let end = capture.node.end_position(); + let mut identifer = Identifier::new(&template, start, end); + identifer.identifier_type = IdentifierType::JinjaTemplate; + self.templates.push(identifer); + self.in_method = false; + } else if name == "method_name" { + let content = capture.node.utf8_text(text.as_bytes()).ok()?; + if !METHODS.contains(&content) { + return None; + } + self.in_method = true; + } + None + } + + pub fn collect(self) -> Vec { + self.templates + } +} + +pub fn rust_templates_query( + query: &Query, + tree: &Tree, + trigger_point: Point, + text: &str, + all: bool, +) -> RustTemplates { + let mut templates = RustTemplates::default(); + let closest_node = tree.root_node(); + let mut cursor_qry = QueryCursor::new(); + let capture_names = query.capture_names(); + let matches = cursor_qry.matches(query, closest_node, text.as_bytes()); + let captures = matches.into_iter().flat_map(|m| { + m.captures + .iter() + .filter(|capture| all || capture.node.start_position() <= trigger_point) + }); + for capture in captures { + let name = &capture_names[capture.index as usize]; + templates.check(name, capture, text); + } + templates +} + +static METHODS: [&str; 2] = ["render_jinja", "get_template"]; diff --git a/jinja-lsp-queries/src/search/snippets_completion.rs b/jinja-lsp-queries/src/search/snippets_completion.rs new file mode 100644 index 0000000..0824e6e --- /dev/null +++ b/jinja-lsp-queries/src/search/snippets_completion.rs @@ -0,0 +1,200 @@ +use crate::to_input_edit::to_position2; +use tower_lsp::lsp_types::{ + CompletionItem, CompletionItemKind, CompletionTextEdit, Range, TextEdit, +}; +use tree_sitter::{Point, Query, QueryCapture, QueryCursor, Tree}; + +#[derive(Default, Debug)] +pub struct Snippets { + start: Point, + end: Point, + keyword: Point, + pub is_error: bool, +} + +impl Snippets { + pub fn check(&mut self, name: &str, capture: &QueryCapture<'_>) -> Option<()> { + match name { + "start" => { + let start = capture.node.start_position(); + self.start = start; + } + "end" => { + let end = capture.node.end_position(); + self.end = end; + } + "error_block" => { + self.is_error = true; + let count = capture.node.child_count(); + if self.start == Point::default() { + self.start = capture.node.start_position(); + } + if count == 0 { + self.start = capture.node.start_position(); + self.end = capture.node.end_position(); + self.end.column += 1; + return None; + } else { + let end = capture.node.start_position(); + let first = capture.node.child(0).unwrap(); + if self.start.row != end.row { + self.start = capture.node.start_position(); + self.end = first.end_position(); + self.end.column += 1; + } else { + let first = capture.node.child(0).unwrap(); + self.end = first.start_position(); + self.end.column += 1; + } + return None; + } + } + "keyword" => { + self.keyword = capture.node.start_position(); + } + _ => (), + } + Some(()) + } + + pub fn to_complete(&self, trigger_point: Point) -> Option { + if self.is_error && trigger_point >= self.start && trigger_point <= self.end { + if self.keyword >= self.start && self.keyword <= self.end { + return None; + } + let start_position = to_position2(trigger_point); + let mut end_position = to_position2(trigger_point); + end_position.character += 1; + return Some(Range::new(start_position, end_position)); + } + None + } +} + +pub fn snippets_query( + query: &Query, + tree: &Tree, + mut trigger_point: Point, + text: &str, + all: bool, +) -> Snippets { + let closest_node = tree.root_node(); + let mut snippets = Snippets::default(); + let mut cursor_qry = QueryCursor::new(); + let capture_names = query.capture_names(); + let matches = cursor_qry.matches(query, closest_node, text.as_bytes()); + trigger_point.column += 2; + let captures = matches.into_iter().flat_map(|m| { + m.captures + .iter() + .filter(|capture| all || capture.node.start_position() <= trigger_point) + }); + for capture in captures { + let name = &capture_names[capture.index as usize]; + let check = snippets.check(name, capture); + if check.is_none() { + break; + } + } + snippets +} + +/// Range will be updated +pub fn snippets() -> Vec { + // label detail text + let all = [ + ( + "for1", + "Basic for loop", + r#" for ${1:i} in ${2:items} %} +{% endfor %} +"#, + ), + ( + "for2", + "For loop with key and value", + r#" for (${1:key}, ${2:value}) in ${3:items} %} +{% endfor %} +"#, + ), + ( + "with", + "With block", + r#" with $1 %} +{% endwith %} +"#, + ), + ( + "set1", + "Set variable that is current scope", + r#" set ${1:key} = ${2:value} %} + "#, + ), + ( + "set2", + "Set with scope", + r#" set ${1:data} %} +{% endset %} + +"#, + ), + ( + "include", + "Include template", + r#" include "$1" %} + "#, + ), + ( + "from", + "Import from other template", + r#" from "$1" import ${2:module} %} + "#, + ), + ( + "import", + "Import entire template as module", + r#" import "$1" as ${2:module} %} + "#, + ), + ( + "extends", + "Extend parent template", + r#" extends "$1" %} + "#, + ), + ( + "if1", + "If statement", + r#" if $1 %} +{% endif %} +"#, + ), + ( + "if2", + "If statement", + r#" if $1 %} +{% elif $2 %} +{% endif %} +"#, + ), + ]; + + let mut snippets = vec![]; + + for snippet in all { + let edit = TextEdit { + new_text: snippet.2.to_owned(), + ..Default::default() + }; + let text_edit = CompletionTextEdit::Edit(edit); + let item = CompletionItem { + label: snippet.0.to_owned(), + detail: Some(snippet.1.to_owned()), + kind: Some(CompletionItemKind::SNIPPET), + text_edit: Some(text_edit), + ..Default::default() + }; + snippets.push(item); + } + snippets +} diff --git a/jinja-lsp-queries/src/search/templates.rs b/jinja-lsp-queries/src/search/templates.rs new file mode 100644 index 0000000..73b6084 --- /dev/null +++ b/jinja-lsp-queries/src/search/templates.rs @@ -0,0 +1,251 @@ +use std::collections::HashMap; + +use tree_sitter::{Point, Query, QueryCapture, QueryCursor, Tree}; + +use super::{Identifier, IdentifierType}; + +#[derive(Debug)] +pub enum Import { + Extends { + template: Identifier, + }, + Include { + templates: Vec, + }, + From { + template: Identifier, + identifiers: Vec, + }, + Import { + template: Identifier, + identifier: Identifier, + }, +} + +impl Import { + pub fn get_identifier(&self, trigger_point: Point) -> Option<&Identifier> { + match &self { + Import::Extends { template } + | Import::From { template, .. } + | Import::Import { template, .. } => { + if trigger_point >= template.start && trigger_point <= template.end { + Some(template) + } else { + None + } + } + Import::Include { templates } => { + let template = templates.iter().find(|template| { + trigger_point >= template.start && trigger_point <= template.end + })?; + Some(template) + } + } + } + + fn collect(self, ids: &mut Vec) { + match self { + Import::Extends { template } => ids.push(template), + Import::Include { templates } => { + for i in templates { + ids.push(i); + } + } + Import::From { + template, + identifiers, + } => { + ids.push(template); + for i in identifiers { + ids.push(i); + } + } + Import::Import { template, .. } => { + ids.push(template); + } + } + } +} + +#[derive(Default)] +pub enum Current { + Id(usize), + #[default] + Nothing, +} + +#[derive(Default)] +pub struct JinjaImports { + pub imports: HashMap, + pub current: Current, + pub last_id: usize, +} + +impl JinjaImports { + pub fn in_template(&self, _: Point) -> Option<&Import> { + if let Current::Id(id) = self.current { + let last = self.imports.get(&id)?; + return Some(last); + } + None + } + pub fn check(&mut self, name: &str, capture: &QueryCapture<'_>, text: &str) -> Option<()> { + match name { + "error" => { + return None; + } + "extends" => { + let id = capture.node.id(); + let last = self.imports.get_mut(&id); + if last.is_some() { + self.current = Current::Nothing; + } else { + let import = Import::Extends { + template: Identifier::default(), + }; + self.imports.insert(id, import); + self.current = Current::Id(id); + self.last_id = id; + } + } + "include" => { + let id = capture.node.id(); + let last = self.imports.get_mut(&id); + if last.is_some() { + self.current = Current::Id(id); + } else { + let import = Import::Include { templates: vec![] }; + self.imports.insert(id, import); + self.current = Current::Id(id); + self.last_id = id; + } + } + "import" => { + let id = capture.node.id(); + let last = self.imports.get_mut(&id); + if last.is_some() { + self.current = Current::Id(id); + } else { + let import = Import::Import { + template: Identifier::default(), + identifier: Identifier::default(), + }; + self.imports.insert(id, import); + self.current = Current::Id(id); + self.last_id = id; + } + } + "from" => { + let id = capture.node.id(); + let last = self.imports.get_mut(&id); + if last.is_some() { + self.current = Current::Id(id); + } else { + let import = Import::From { + template: Identifier::default(), + identifiers: vec![], + }; + self.imports.insert(id, import); + self.current = Current::Id(id); + self.last_id = id; + } + } + "template_name" => { + if let Current::Id(id) = self.current { + let last = self.imports.get_mut(&id)?; + let name = capture.node.utf8_text(text.as_bytes()).ok()?; + let name = name.replace(['\"', '\''], ""); + let start = capture.node.start_position(); + let end = capture.node.end_position(); + match last { + Import::Extends { template } => { + if template.name.is_empty() { + template.name = name; + template.start = start; + template.end = end; + template.identifier_type = IdentifierType::JinjaTemplate; + } + } + Import::Include { templates } => { + let mut template = Identifier::new(&name, start, end); + template.identifier_type = IdentifierType::JinjaTemplate; + templates.push(template); + } + Import::From { template, .. } => { + if template.name.is_empty() { + template.name = name; + template.start = start; + template.end = end; + template.identifier_type = IdentifierType::JinjaTemplate; + } + } + Import::Import { template, .. } => { + if template.name.is_empty() { + template.name = name; + template.start = start; + template.end = end; + template.identifier_type = IdentifierType::JinjaTemplate; + } + } + } + } + } + "import_identifier" => { + if let Current::Id(id) = self.current { + let name = capture.node.utf8_text(text.as_bytes()).ok()?; + let start = capture.node.start_position(); + let end = capture.node.end_position(); + let last = self.imports.get_mut(&id)?; + match last { + Import::From { identifiers, .. } => { + let identifier = Identifier::new(name, start, end); + identifiers.push(identifier); + } + Import::Import { identifier, .. } => { + if identifier.name.is_empty() { + identifier.name = String::from(name); + self.current = Current::Nothing; + } + } + _ => self.current = Current::Nothing, + } + } + } + + _ => (), + } + Some(()) + } + + pub fn collect(self, ids: &mut Vec) { + for i in self.imports { + i.1.collect(ids); + } + } +} +pub fn templates_query( + query: &Query, + tree: &Tree, + trigger_point: Point, + text: &str, + all: bool, +) -> JinjaImports { + let closest_node = tree.root_node(); + let mut imports = JinjaImports::default(); + let mut cursor_qry = QueryCursor::new(); + let capture_names = query.capture_names(); + let matches = cursor_qry.matches(query, closest_node, text.as_bytes()); + let captures = matches.into_iter().flat_map(|m| { + m.captures + .iter() + .filter(|capture| all || capture.node.start_position() <= trigger_point) + }); + for capture in captures { + let name = &capture_names[capture.index as usize]; + let res = imports.check(name, capture, text); + if res.is_none() { + break; + } + } + imports +} diff --git a/jinja-lsp-queries/src/search/test_queries.rs b/jinja-lsp-queries/src/search/test_queries.rs new file mode 100644 index 0000000..90c2e96 --- /dev/null +++ b/jinja-lsp-queries/src/search/test_queries.rs @@ -0,0 +1,311 @@ +#[cfg(test)] +mod query_tests { + use crate::search::{objects::objects_query, snippets_completion::snippets_query}; + use tree_sitter::{Parser, Point}; + + use crate::{ + search::objects::CompletionType, + search::{ + completion_start, definition::definition_query, queries::Queries, + rust_identifiers::rust_definition_query, + rust_template_completion::rust_templates_query, templates::templates_query, + }, + }; + + fn prepare_jinja_tree(text: &str) -> tree_sitter::Tree { + let language = tree_sitter_jinja2::language(); + let mut parser = Parser::new(); + + parser + .set_language(language) + .expect("could not load jinja grammar"); + + parser.parse(text, None).expect("not to fail") + } + + fn prepare_rust_tree(text: &str) -> tree_sitter::Tree { + let language = tree_sitter_rust::language(); + let mut parser = Parser::new(); + + parser + .set_language(language) + .expect("could not load rust grammar"); + + parser.parse(text, None).expect("not to fail") + } + + #[test] + fn jinja_definitions() { + let cases = [ + ( + r#" + {% macro do_something(a, b,c) %} +

Hello world

+ {% set class = "button" -%} + {% with name = 55 %} +

Hello {{ name }}

+ {% endwith %} + {% endmacro %} + + {% for i in 10 -%} + {%- endfor %} + + {{ point }} + {{ point }} + "#, + 7, + ), + ( + r#" + {% with name = 55 %} +

Hello {{ name }}

+ {% set a = "hello world" %} + {% set b %} + some content + {% endset %} + {% endwith %} + {% for i in 10 -%} + {%- endfor %} + + {{ point }} + {{ point }} + "#, + 4, + ), + ]; + let query = Queries::default(); + let query = query.jinja_definitions; + + for case in cases { + let tree = prepare_jinja_tree(case.0); + let trigger_point = Point::new(0, 0); + // let closest_node = tree.root_node(); + let definitions = definition_query(&query, &tree, trigger_point, case.0, true); + assert_eq!(definitions.identifiers().len(), case.1); + } + } + + #[test] + fn jinja_identifiers() { + let query = Queries::default(); + let query = query.jinja_objects; + let cases = [ + ( + r#" + {{ user.id }} + {% for i in 10 -%} + {{ i }} + {%- endfor %} + {% set class = "button" -%} + "#, + 4, + ), + ( + r#" + {{ obj.abc obj2.abc2 }} + + {{ obj3.field.something.something == obj4.something }} + + {% if obj5.field -%} + 111 {{ abc == def.abc }} + {% endif %} + "#, + 7, + ), + ( + r#" +

{{ something }}

+

{{ something | some_filter(a, b,c) }}

+ {% for i in something -%} + {{ i }} + {%- endfor %} + {% if something %} + {{ something }} + {% endif %} + "#, + 11, + ), + ]; + for case in cases { + let tree = prepare_jinja_tree(case.0); + let trigger_point = Point::new(0, 0); + let objects = objects_query(&query, &tree, trigger_point, case.0, true); + let len = objects.show().len(); + assert_eq!(len, case.1); + } + } + + #[test] + fn rust_macros() { + let case = r#" + let a = context!(name => 11 + abc, abc => "username"); + let b = context!{name, username => "username" } + let price = 100; + let c = context!{ price }; + jinja.add_filter("running_locally", true); + jinja.add_function("some_fn", some_fn); + "#; + + let tree = prepare_rust_tree(case); + let trigger_point = Point::new(0, 0); + let query = Queries::default(); + let query = &query.rust_definitions; + let rust = rust_definition_query(query, &tree, trigger_point, case, true); + assert_eq!(rust.show().len(), 8); + } + + #[test] + fn find_jinja_completion() { + let source = r#" + {{ something | filter1 | filter2 }} + + {% if something == 11 -%} + {% macro example(a, b, c) -%} +

hello world

+ {%- endmacro %} + + {{ }} + {{ "|" }} + "#; + let cases = [ + (Point::new(1, 27), Some(CompletionType::Filter)), + (Point::new(1, 48), None), + (Point::new(1, 40), Some(CompletionType::Filter)), + (Point::new(1, 50), Some(CompletionType::Identifier)), + (Point::new(3, 18), None), + (Point::new(4, 20), None), + (Point::new(3, 22), None), + (Point::new(8, 15), Some(CompletionType::Identifier)), + (Point::new(9, 18), Some(CompletionType::Identifier)), + ]; + for case in cases { + let tree = prepare_jinja_tree(source); + let trigger_point = case.0; + let query = Queries::default(); + let query = &query.jinja_objects; + let objects = objects_query(query, &tree, trigger_point, source, false); + assert_eq!(objects.completion(trigger_point), case.1); + } + } + + #[test] + fn check_jinja_templates() { + let source = r#" +
+ {% include 'header.jinja' %} + {% include 'customization.jinja' ignore missing %} + {% include ['page_detailed.jinja', 'page.jinja'] %} + {% import "header.jinja" %} + {% from "page_detailed.jinja" import a %} +
+ "#; + + let cases = [ + (Point::new(2, 25), "header.jinja"), + (Point::new(3, 30), "customization.jinja"), + (Point::new(4, 36), "page_detailed.jinja"), + (Point::new(4, 50), "page.jinja"), + (Point::new(5, 34), "header.jinja"), + (Point::new(6, 39), "page_detailed.jinja"), + ]; + for case in cases { + let tree = prepare_jinja_tree(source); + let trigger_point = case.0; + let query = Queries::default(); + let query = &query.jinja_imports; + let templates = templates_query(query, &tree, trigger_point, source, false); + let template = templates.in_template(trigger_point); + assert!(template.is_some()); + assert_eq!( + template + .unwrap() + .get_identifier(trigger_point) + .unwrap() + .name, + case.1 + ); + } + } + + #[test] + fn jinja_templates_in_rust() { + let source = r#" + get_template("",11); + render_jinja("some_template.jinja"); + render_jinja(1,2, 3, "some_template.jinja"); + render_jinja(1,2, 3); + add_global("PROJECT_NAME", "Example"); + + "#; + let tree = prepare_rust_tree(source); + let trigger_point = Point::default(); + let query = Queries::default(); + let query = &query.rust_templates; + let templates = rust_templates_query(query, &tree, trigger_point, source, true); + assert_eq!(templates.templates.len(), 3); + } + + #[test] + fn template_completion_in_rust() { + let source = r#" + let tmp2 = jinja.get_template("account3"); + let tmp2 = jinja.get_template("account2"); + let tmp = jinja.get_template("account"); + let tmp = jinja.anything("account"); + "#; + let tree = prepare_rust_tree(source); + let trigger_point = Point::new(3, 47); + let query = Queries::default(); + let query = &query.rust_templates; + let templates = rust_templates_query(query, &tree, trigger_point, source, false); + if let Some(template) = templates.in_template(trigger_point) { + if let Some(completion) = completion_start(trigger_point, template) { + assert_eq!(completion, "accoun"); + } + } + } + + #[test] + fn jinja_definition_scope() { + let source = r#" + {% macro hello_world(parameter) -%} +

hello world

+ {{ PROJECT_NAME | length }} + {{ parameter }} + {% macro primer(parameter2) %} + {{ parameter }} + {% endmacro %} + {% set b = 11 %} + {% endmacro %} + "#; + let query = Queries::default(); + let query = query.jinja_definitions; + let tree = prepare_jinja_tree(source); + let trigger_point = Point::new(0, 0); + let definitions = definition_query(&query, &tree, trigger_point, source, true); + let keys = definitions.identifiers(); + if let Some(last) = keys.last() { + assert_eq!(last.scope_ends.1, Point::new(9, 4)); + } + } + + #[test] + fn snippets() { + let cases = [ + ( + "{% if} {% if a == 123 %} {{ a }} {% endif %}", + Point::new(0, 5), + true, + ), + ("{% with} {{ var }} {% with %}", Point::new(0, 26), true), + ("{% with ", Point::new(0, 9), false), + ]; + let query = Queries::default(); + let query = query.jinja_snippets; + for case in cases { + let tree = prepare_jinja_tree(case.0); + let snippets = snippets_query(&query, &tree, case.1, case.0, false); + assert_eq!(snippets.is_error, case.2); + } + } +} diff --git a/jinja-lsp-queries/src/test_queries.rs b/jinja-lsp-queries/src/test_queries.rs deleted file mode 100644 index 664a93b..0000000 --- a/jinja-lsp-queries/src/test_queries.rs +++ /dev/null @@ -1,258 +0,0 @@ -#[cfg(test)] -mod query_tests { - - use crate::capturer::included::IncludeCapturer; - use tree_sitter::{Parser, Point}; - - use crate::{ - capturer::{ - init::JinjaInitCapturer, - object::{CompletionType, JinjaObjectCapturer}, - rust::RustCapturer, - }, - queries::{query_props, Queries}, - }; - - fn prepare_jinja_tree(text: &str) -> tree_sitter::Tree { - let language = tree_sitter_jinja2::language(); - let mut parser = Parser::new(); - - parser - .set_language(language) - .expect("could not load jinja grammar"); - - parser.parse(text, None).expect("not to fail") - } - - fn prepare_rust_tree(text: &str) -> tree_sitter::Tree { - let language = tree_sitter_rust::language(); - let mut parser = Parser::new(); - - parser - .set_language(language) - .expect("could not load rust grammar"); - - parser.parse(text, None).expect("not to fail") - } - - #[test] - fn find_ident_definition() { - let case = r#" - {% macro do_something(a, b,c) %} -

Hello world

- {% set class = "button" -%} - {% with name = 55 %} -

Hello {{ name }}

- {% endwith %} - - {% endmacro %} - - - {% for i in 10 -%} - {%- endfor %} - - {{ point }} - {{ point }} - "#; - let tree = prepare_jinja_tree(case); - let trigger_point = Point::new(0, 0); - let closest_node = tree.root_node(); - let query = Queries::default(); - let query = &query.jinja_init; - let capturer = JinjaInitCapturer::default(); - let capturer = query_props(closest_node, case, trigger_point, query, true, capturer); - assert_eq!(capturer.to_vec().len(), 7); - } - - #[test] - fn find_identifiers() { - let case = r#" - {{ user.id }} - {% for i in 10 -%} - {{ i }} - {%- endfor %} - {% set class = "button" -%} - "#; - let tree = prepare_jinja_tree(case); - let trigger_point = Point::new(0, 0); - let closest_node = tree.root_node(); - let query = Queries::default(); - let query = &query.jinja_idents; - let capturer = JinjaObjectCapturer::default(); - let props = query_props(closest_node, case, trigger_point, query, true, capturer); - assert_eq!(props.show().len(), 4); - } - - #[test] - fn find_identifiers_with_statements_and_expressions() { - let case = r#" - {{ obj.abc obj2.abc2 }} - - {{ obj3.field.something.something == obj4.something }} - - {% if obj5.field -%} - 111 {{ abc == def.abc }} - {% endif %} - "#; - let tree = prepare_jinja_tree(case); - let trigger_point = Point::new(0, 0); - let closest_node = tree.root_node(); - let query = Queries::default(); - let query = &query.jinja_idents; - let capturer = JinjaObjectCapturer::default(); - let props = query_props(closest_node, case, trigger_point, query, true, capturer); - assert_eq!(props.show().len(), 7); - } - - #[test] - fn find_identifiers_quick() { - let case = r#" -

{{ something }}

-

{{ something | some_filter(a, b,c) }}

- {% for i in something -%} - {{ i }} - {%- endfor %} - {% if something %} - {{ something }} - {% endif %} - "#; - let tree = prepare_jinja_tree(case); - let trigger_point = Point::new(0, 0); - let closest_node = tree.root_node(); - let query = Queries::default(); - let query = &query.jinja_idents; - let capturer = JinjaObjectCapturer::default(); - let props = query_props(closest_node, case, trigger_point, query, true, capturer); - assert_eq!(props.show().len(), 11); - } - - #[test] - fn find_identifiers_in_macro() { - let case = r#" - let a = context!(name => 11 + abc, abc => "username"); - let b = context!{name, username => "username" } - let price = 100; - let c = context!{ price }; - jinja.add_filter("running_locally", true); - jinja.add_function("some_fn", some_fn); - "#; - - let tree = prepare_rust_tree(case); - let trigger_point = Point::new(0, 0); - let closest_node = tree.root_node(); - let query = Queries::default(); - let query = &query.rust_idents; - let capturer = RustCapturer::default(); - let props = query_props(closest_node, case, trigger_point, query, true, capturer); - let macros = props.macros(); - assert_eq!(macros.len(), 3); - let mut count = 0; - for context in macros { - count += context.1.variables().len(); - } - let variables = props.variables(); - count += variables.len(); - assert_eq!(count, 7); - } - - #[test] - fn find_jinja_completion() { - let source = r#" - {{ something | filter1 | filter2 }} - - {% if something == 11 -%} - {% macro example(a, b, c) -%} -

hello world

- {%- endmacro %} - - {{ }} - {{ "|" }} - "#; - let cases = [ - (Point::new(1, 27), Some(CompletionType::Filter)), - (Point::new(1, 48), None), - (Point::new(1, 40), Some(CompletionType::Filter)), - (Point::new(1, 50), Some(CompletionType::Identifier)), - (Point::new(3, 18), None), - (Point::new(4, 20), None), - (Point::new(3, 22), None), - (Point::new(8, 15), Some(CompletionType::Identifier)), - (Point::new(9, 18), Some(CompletionType::Identifier)), - ]; - for case in cases { - let tree = prepare_jinja_tree(source); - let trigger_point = case.0; - let closest_node = tree.root_node(); - let query = Queries::default(); - - let query = &query.jinja_idents; - let capturer = JinjaObjectCapturer::default(); - let props = query_props(closest_node, source, trigger_point, query, false, capturer); - assert_eq!(props.completion(trigger_point), case.1); - } - } - - #[test] - fn find_includes() { - let source = r#" -
- {% include 'header.jinja' %} - {% include 'customization.jinja' ignore missing %} - {% include ['page_detailed.jinja', 'page.jinja'] %} -
- "#; - let cases = [ - (Point::new(2, 31), "header.jinja"), - (Point::new(3, 23), "customization.jinja"), - (Point::new(4, 62), "page.jinja"), - ]; - for case in cases { - let tree = prepare_jinja_tree(source); - let trigger_point = case; - let closest_node = tree.root_node(); - let query = Queries::default(); - - let query = &query.jinja_imports; - let capturer = IncludeCapturer::default(); - let props = query_props( - closest_node, - source, - trigger_point.0, - query, - false, - capturer, - ); - let template = props.in_template(case.0); - assert!(template.is_some()); - assert_eq!(&template.unwrap().name, &case.1); - } - } - - #[test] - fn import() { - let source = r#" - {% set a = 11 %} - {% from "some_template" import b %} - "#; - let tree = prepare_jinja_tree(source); - let trigger_point = Point::default(); - let closest_node = tree.root_node(); - let query = Queries::default(); - - let query = &query.jinja_init; - let capturer = JinjaInitCapturer::default(); - let props = query_props(closest_node, source, trigger_point, query, true, capturer); - assert_eq!(props.states.len(), 2); - } - - // #[test] - // fn included_template_completion() { - // let source = r#" - //
- // {% include " - //
- // "#; - - // let cases = [(Point::new(2, 28), false, true, false)]; - // } -} diff --git a/jinja-lsp-queries/src/to_input_edit.rs b/jinja-lsp-queries/src/to_input_edit.rs index cbf790a..a672b53 100644 --- a/jinja-lsp-queries/src/to_input_edit.rs +++ b/jinja-lsp-queries/src/to_input_edit.rs @@ -2,8 +2,6 @@ use ropey::Rope; use tower_lsp::lsp_types::{Position, Range}; use tree_sitter::{InputEdit, Point}; -use crate::tree_builder::JinjaVariable; - // use crate::lsp_files::JinjaVariable; pub trait ToInputEdit { @@ -71,19 +69,6 @@ impl ToInputEdit for Rope { } } -pub fn to_position(variable: &JinjaVariable) -> (Position, Position) { - ( - Position::new( - variable.location.0.row as u32, - variable.location.0.column as u32, - ), - Position::new( - variable.location.1.row as u32, - variable.location.1.column as u32, - ), - ) -} - pub fn to_position2(point: Point) -> Position { Position::new(point.row as u32, point.column as u32) } diff --git a/jinja-lsp-queries/src/tree_builder.rs b/jinja-lsp-queries/src/tree_builder.rs index 0e8c400..896c830 100644 --- a/jinja-lsp-queries/src/tree_builder.rs +++ b/jinja-lsp-queries/src/tree_builder.rs @@ -1,4 +1,4 @@ -use tree_sitter::{Node, Point, QueryCapture}; +use tower_lsp::lsp_types::DiagnosticSeverity; #[derive(PartialEq, Eq, Debug, Copy, Clone, Hash)] pub enum LangType { @@ -6,348 +6,22 @@ pub enum LangType { Backend, } -#[derive(Clone, Debug)] -pub enum JinjaKeyword { - For { - key: String, - value: String, - passed_open_paren: bool, - }, - Macro { - name: String, - parameters: Vec<(String, (Point, Point))>, - }, - Block { - name: String, - }, - Set { - name: String, - equals: bool, - }, - From { - name: String, - from: String, - }, - With { - name: String, - }, - NoKeyword, -} - -#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum DataType { - Macro, - MacroParameter, - Variable, - BackendVariable, - WithVariable, - Block, - Template, -} - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct JinjaVariable { - pub location: (Point, Point), - pub name: String, - pub data_type: DataType, -} - -impl JinjaVariable { - pub fn new(name: &String, location: (Point, Point), data_type: DataType) -> Self { - Self { - name: String::from(name), - location, - data_type, - } - } -} - -impl JinjaKeyword { - pub fn add_identifier(&mut self, identifier: &str, range: (Point, Point)) -> Option<()> { - match self { - JinjaKeyword::For { key, value, .. } => { - if key.is_empty() { - *key = String::from(identifier); - Some(()) - } else if value.is_empty() { - *value = String::from(identifier); - Some(()) - } else { - None - } - } - JinjaKeyword::Macro { name, parameters } => { - if name.is_empty() { - *name = String::from(identifier); - Some(()) - } else { - parameters.push((String::from(identifier), range)); - Some(()) - } - } - JinjaKeyword::Block { name } => { - if name.is_empty() { - *name = String::from(identifier); - Some(()) - } else { - None - } - } - JinjaKeyword::Set { name, .. } => { - if name.is_empty() { - *name = String::from(identifier); - Some(()) - } else { - None - } - } - JinjaKeyword::From { name, from } => { - let from2 = identifier.replace(['"', '\''], ""); - if from.is_empty() { - *from = from2; - Some(()) - } - //else if name.is_empty() { - else { - None - } - // *name = String::from(identifier); - // Some(()) - // } else { - // None - // } - } - JinjaKeyword::With { name } => { - if name.is_empty() { - *name = String::from(identifier); - Some(()) - } else { - None - } - } - JinjaKeyword::NoKeyword => None, - } - } - - pub fn add_operator(&mut self, operator: &str) -> Option<()> { - match self { - JinjaKeyword::For { - passed_open_paren, .. - } => { - if !passed_open_paren.to_owned() && operator.starts_with('(') { - *passed_open_paren = true; - } - None - } - JinjaKeyword::Set { equals, .. } => { - if !*equals && operator.starts_with('=') { - *equals = true; - Some(()) - } else { - None - } - } - _ => Some(()), - } - } - - pub fn get_data(&self, all: &mut Vec, data: &IdentifierState) { - match self { - JinjaKeyword::For { key, value, .. } => { - let key = JinjaVariable::new(key, data.location, DataType::Variable); - all.push(key); - if !value.is_empty() { - let value = JinjaVariable::new(value, data.location, DataType::Variable); - all.push(value); - } - } - JinjaKeyword::Macro { name, parameters } => { - let name = JinjaVariable::new(name, data.location, DataType::Macro); - all.push(name); - for param in parameters { - let param = JinjaVariable::new(¶m.0, param.1, DataType::MacroParameter); - all.push(param); - } - } - JinjaKeyword::Block { name } => { - let name = JinjaVariable::new(name, data.location, DataType::Block); - all.push(name); - } - JinjaKeyword::Set { name, equals: _ } => { - let name = JinjaVariable::new(name, data.location, DataType::Variable); - all.push(name); - } - JinjaKeyword::From { name: _, from } => { - let from = JinjaVariable::new(from, data.location, DataType::Template); - all.push(from); - } - JinjaKeyword::With { name } => { - let name = JinjaVariable::new(name, data.location, DataType::WithVariable); - all.push(name); - } - JinjaKeyword::NoKeyword => { - // - } - } - } -} - -pub static KEYWORDS: [&str; 8] = [ - "for", "macro", "block", "set", "from", "import", "with", "include", -]; - -#[derive(Clone, Default, Debug)] -pub struct IdentifierState { - pub keyword: JinjaKeyword, - pub location: (Point, Point), - pub statement_started: bool, - pub statement_ended: bool, - pub id: usize, - pub have_keyword: bool, -} - -impl IdentifierState { - pub fn parse_start_statement(&mut self, capture: &QueryCapture<'_>, source: &str) { - let mut walker = capture.node.walk(); - let children = capture.node.children(&mut walker); - self.id = capture.node.id(); - for child in children { - match child.kind_id() { - 57 => self.statement_started = true, - 58 => self.statement_ended = true, - 63 => self.add_keyword(child, source), - 1 => self.add_identifier(child, source), - 50 => self.add_operator(child, source), - 51 => self.add_identifier(child, source), - _ => (), - } - } - } - - pub fn parse_end_statement(&mut self, _capture: &QueryCapture<'_>, _source: &str) { - // let mut c2 = capture.node.walk(); - // let children = capture.node.children(&mut c2); - // for child in children { - // match child.kind_id() { - // 26 => (), - // _ => (), - // } - // } - } - - pub fn add_keyword(&mut self, child: Node<'_>, source: &str) { - if self.have_keyword || !self.statement_started || self.statement_ended { - return; - } - let kw = child.utf8_text(source.as_bytes()); - if let Ok(kw) = kw { - if !KEYWORDS.contains(&kw) { - return; - } - let kw = JinjaKeyword::try_from(kw); - if let Ok(kw) = kw { - self.keyword = kw - } - self.have_keyword = true; - } - } - - pub fn add_identifier(&mut self, child: Node<'_>, source: &str) { - if !self.have_keyword || !self.statement_started || self.statement_ended { - return; - } - let identifier = child.utf8_text(source.as_bytes()); - let start = child.start_position(); - let end = child.end_position(); - if let Some(_s) = identifier - .ok() - .and_then(|id| { - if id.parse::().is_ok() { - None - } else { - Some(id) - } - }) - .and_then(|id| -> Option<()> { self.keyword.add_identifier(id, (start, end)) }) - { - self.have_keyword = true; - if self.location.0.row == 0 && self.location.0.column == 0 { - self.location = (start, end); - } - } - } - - fn add_operator(&mut self, child: Node<'_>, source: &str) { - if !self.have_keyword || !self.statement_started || self.statement_ended { - return; - } - let operator = child.utf8_text(source.as_bytes()); - operator - .ok() - .and_then(|operator| { - if operator.starts_with('(') - || operator.starts_with(',') - || operator.starts_with(')') - || operator.starts_with('=') - { - Some(operator) - } else { - None - } - }) - .and_then(|operator| self.keyword.add_operator(operator)); - } -} - -impl Default for JinjaKeyword { - fn default() -> Self { - Self::NoKeyword - } -} - -impl TryFrom<&str> for JinjaKeyword { - type Error = (); - - fn try_from(value: &str) -> Result { - let keyword = match value { - "for" => Some(JinjaKeyword::For { - key: String::new(), - value: String::new(), - passed_open_paren: false, - }), - "macro" => Some(JinjaKeyword::Macro { - name: String::new(), - parameters: vec![], - }), - "block" => Some(JinjaKeyword::Block { - name: String::new(), - }), - "set" => Some(JinjaKeyword::Set { - name: String::new(), - equals: false, - }), - "from" | "include" => Some(JinjaKeyword::From { - name: String::new(), - from: String::new(), - }), - "with" => Some(JinjaKeyword::With { - name: String::new(), - }), - _ => None, - }; - match keyword.is_none() { - true => Err(()), - false => Ok(keyword.unwrap()), - } - } -} - pub enum JinjaDiagnostic { DefinedSomewhere, Undefined, TemplateNotFound, } +impl JinjaDiagnostic { + pub fn severity(&self) -> DiagnosticSeverity { + match &self { + JinjaDiagnostic::DefinedSomewhere => DiagnosticSeverity::INFORMATION, + JinjaDiagnostic::Undefined => DiagnosticSeverity::WARNING, + JinjaDiagnostic::TemplateNotFound => DiagnosticSeverity::WARNING, + } + } +} + impl ToString for JinjaDiagnostic { fn to_string(&self) -> String { match self { diff --git a/jinja-lsp/Cargo.toml b/jinja-lsp/Cargo.toml index c810992..f2babdb 100644 --- a/jinja-lsp/Cargo.toml +++ b/jinja-lsp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jinja-lsp" -version = "0.1.61" +version = "0.1.70" edition = "2021" license = "MIT" authors = ["uros-5"] @@ -25,9 +25,9 @@ serde_json = "1.0.78" tokio = { version = "1.17.0", features = ["full"] } tower-lsp = { version = "0.20.0", features = ["proposed"]} serde = { version = "1.0", features = ["derive"] } -tree-sitter = "0.20.10" +tree-sitter = "^0.20.10" walkdir = "2.4.0" anyhow = "1.0.75" -tree-sitter-jinja2 = "0.0.5" -tree-sitter-rust = "0.20.4" -jinja-lsp-queries = { path = "../jinja-lsp-queries", version = "0.1.61"} +tree-sitter-jinja2 = "0.0.6" +tree-sitter-rust = "^0.20.4" +jinja-lsp-queries = { path = "../jinja-lsp-queries", version = "0.1.70"} diff --git a/jinja-lsp/src/backend.rs b/jinja-lsp/src/backend.rs index 7212732..d95e1f4 100644 --- a/jinja-lsp/src/backend.rs +++ b/jinja-lsp/src/backend.rs @@ -6,8 +6,9 @@ use tokio::sync::{ use tower_lsp::{ jsonrpc::Result, lsp_types::{ - CompletionParams, CompletionResponse, DidCloseTextDocumentParams, - DidOpenTextDocumentParams, InitializeParams, InitializeResult, + CompletionParams, CompletionResponse, DidChangeConfigurationParams, + DidCloseTextDocumentParams, DidOpenTextDocumentParams, DocumentSymbolParams, + DocumentSymbolResponse, InitializeParams, InitializeResult, }, Client, LanguageServer, }; @@ -43,18 +44,11 @@ impl LanguageServer for Backend { } async fn initialized(&self, _params: InitializedParams) { - let (sender, rx) = oneshot::channel(); + let (sender, _) = oneshot::channel(); let _ = self .main_channel .send(LspMessage::Initialized(sender)) .await; - if let Ok(msg) = rx.await { - if !msg { - let _ = self.shutdown().await; - } - } else { - let _ = self.shutdown().await; - } } async fn did_open(&self, params: DidOpenTextDocumentParams) { @@ -131,6 +125,29 @@ impl LanguageServer for Backend { Ok(None) } + async fn document_symbol( + &self, + params: DocumentSymbolParams, + ) -> Result> { + let (sender, tx) = oneshot::channel(); + let _ = self + .main_channel + .send(LspMessage::DocumentSymbol(params, sender)) + .await; + if let Ok(symbols) = tx.await { + return Ok(symbols); + } + + Ok(None) + } + + async fn did_change_configuration(&self, params: DidChangeConfigurationParams) { + let _ = self + .main_channel + .send(LspMessage::DidChangeConfiguration(params)) + .await; + } + async fn shutdown(&self) -> Result<()> { Ok(()) } @@ -157,7 +174,7 @@ pub fn code_actions() -> Vec { } impl Backend { pub fn new(client: Client) -> Self { - let (lsp_sender, lsp_recv) = mpsc::channel(20); + let (lsp_sender, lsp_recv) = mpsc::channel(50); let (diagnostic_sender, diagnostic_recv) = mpsc::channel(20); lsp_task( client.clone(), diff --git a/jinja-lsp/src/channels/diagnostics.rs b/jinja-lsp/src/channels/diagnostics.rs index 34ba383..a9558d8 100644 --- a/jinja-lsp/src/channels/diagnostics.rs +++ b/jinja-lsp/src/channels/diagnostics.rs @@ -1,9 +1,8 @@ use std::collections::HashMap; -use jinja_lsp_queries::tree_builder::{JinjaDiagnostic, JinjaVariable}; use tokio::sync::mpsc::Receiver; use tower_lsp::{ - lsp_types::{Diagnostic, DiagnosticSeverity, MessageType, Position, Range, Url}, + lsp_types::{Diagnostic, MessageType, Url}, Client, }; @@ -11,77 +10,19 @@ pub fn diagnostics_task(client: Client, mut receiver: Receiver { - let mut hm: HashMap> = HashMap::new(); - let mut added = false; - for (file, diags) in diagnostics { - for (variable, diag2) in diags { - let severity = { - match diag2 { - JinjaDiagnostic::DefinedSomewhere => { - DiagnosticSeverity::INFORMATION - } - JinjaDiagnostic::Undefined => DiagnosticSeverity::WARNING, - JinjaDiagnostic::TemplateNotFound => { - DiagnosticSeverity::WARNING - } - } - }; - added = true; - - let diagnostic = Diagnostic { - range: Range::new( - Position::new( - variable.location.0.row as u32, - variable.location.0.column as u32, - ), - Position::new( - variable.location.1.row as u32, - variable.location.1.column as u32, - ), - ), - severity: Some(severity), - message: diag2.to_string(), - source: Some(String::from("jinja-lsp")), - ..Default::default() - }; - - if hm.contains_key(&file) { - let _ = hm.get_mut(&file).is_some_and(|d| { - d.push(diagnostic); - false - }); - } else { - hm.insert(String::from(&file), vec![diagnostic]); - } - } - } - - for (url, diagnostics) in hm { - if let Ok(uri) = Url::parse(&url) { - client.publish_diagnostics(uri, diagnostics, None).await; - } - } - if let Some(uri) = current_file { - if !added { - let uri = Url::parse(&uri).unwrap(); - client.publish_diagnostics(uri, vec![], None).await; - } + DiagnosticMessage::Str(msg) => client.log_message(MessageType::INFO, msg).await, + DiagnosticMessage::Errors(all_errors) => { + for (uri, errors) in all_errors.into_iter() { + let uri = Url::parse(&uri).unwrap(); + client.publish_diagnostics(uri, errors, None).await; } } - DiagnosticMessage::Str(msg) => client.log_message(MessageType::INFO, msg).await, } } }); } pub enum DiagnosticMessage { - Errors { - diagnostics: HashMap>, - current_file: Option, - }, + Errors(HashMap>), Str(String), } diff --git a/jinja-lsp/src/channels/lsp.rs b/jinja-lsp/src/channels/lsp.rs index 20dbaf0..4d06e7f 100644 --- a/jinja-lsp/src/channels/lsp.rs +++ b/jinja-lsp/src/channels/lsp.rs @@ -1,16 +1,18 @@ -use jinja_lsp_queries::capturer::object::CompletionType; +use jinja_lsp_queries::search::{objects::CompletionType, snippets_completion::snippets}; use serde_json::Value; use tokio::sync::{mpsc, oneshot}; use tower_lsp::{ lsp_types::{ CodeActionParams, CodeActionProviderCapability, CodeActionResponse, CompletionItem, CompletionItemKind, CompletionOptions, CompletionParams, CompletionResponse, - DidChangeTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, - Documentation, ExecuteCommandOptions, ExecuteCommandParams, GotoDefinitionParams, - GotoDefinitionResponse, Hover, HoverContents, HoverParams, HoverProviderCapability, - InitializeParams, InitializeResult, MarkupContent, MarkupKind, MessageType, OneOf, - ServerCapabilities, ServerInfo, TextDocumentSyncCapability, TextDocumentSyncKind, - TextDocumentSyncOptions, TextDocumentSyncSaveOptions, + CompletionTextEdit, DidChangeConfigurationParams, DidChangeTextDocumentParams, + DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentSymbolParams, + DocumentSymbolResponse, Documentation, ExecuteCommandOptions, ExecuteCommandParams, + GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams, + HoverProviderCapability, InitializeParams, InitializeResult, InsertReplaceEdit, + MarkupContent, MarkupKind, MessageType, OneOf, ServerCapabilities, ServerInfo, + TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, + TextDocumentSyncSaveOptions, TextEdit, }, Client, }; @@ -19,7 +21,7 @@ use crate::{ backend::code_actions, config::{walkdir, JinjaConfig}, filter::init_filter_completions, - lsp_files::LspFiles, + lsp_files2::LspFiles, }; use super::diagnostics::DiagnosticMessage; @@ -30,18 +32,17 @@ pub fn lsp_task( lsp_channel: mpsc::Sender, mut lsp_recv: mpsc::Receiver, ) { - // let mut documents = HashMap::new(); - let mut can_complete = false; let mut config = JinjaConfig::default(); let mut lsp_data = LspFiles::default(); let filters = init_filter_completions(); + let snippets = snippets(); tokio::spawn(async move { while let Some(msg) = lsp_recv.recv().await { match msg { LspMessage::Initialize(params, sender) => { if let Some(client_info) = params.client_info { - if client_info.name == "helix" { - can_complete = true; + if client_info.name == "Visual Studio Code" { + lsp_data.is_vscode = true; } } params @@ -54,11 +55,6 @@ pub fn lsp_task( None }); - if !config.user_defined { - drop(sender); - continue; - } - let definition_provider = Some(OneOf::Left(true)); let references_provider = None; let code_action_provider = Some(CodeActionProviderCapability::Simple(true)); @@ -67,6 +63,7 @@ pub fn lsp_task( commands: vec!["reset_variables".to_string(), "warn".to_string()], ..Default::default() }); + let document_symbol_provider = Some(OneOf::Left(true)); let msg = InitializeResult { capabilities: ServerCapabilities { @@ -84,6 +81,7 @@ pub fn lsp_task( "-".to_string(), "\"".to_string(), " ".to_string(), + "%".to_string(), ]), all_commit_characters: None, work_done_progress_options: Default::default(), @@ -93,12 +91,13 @@ pub fn lsp_task( references_provider, code_action_provider, execute_command_provider, + document_symbol_provider, hover_provider, ..ServerCapabilities::default() }, server_info: Some(ServerInfo { name: String::from("jinja-lsp"), - version: Some(String::from("0.1.61")), + version: Some(String::from("0.1.62")), }), offset_encoding: None, }; @@ -108,31 +107,27 @@ pub fn lsp_task( client.log_message(MessageType::INFO, "Initialized").await; if !config.user_defined { client - .log_message(MessageType::INFO, "Config doesn't exist.") + .log_message(MessageType::WARNING, "Config doesn't exist.") .await; - break; } if config.templates.is_empty() { client - .log_message(MessageType::INFO, "Template directory not found") + .log_message(MessageType::WARNING, "Template directory not found") .await; - break; } if config.lang != "rust" { client - .log_message(MessageType::INFO, "Backend language not supported") + .log_message(MessageType::WARNING, "Backend language not supported") .await; - break; } else { match walkdir(&config) { Ok(errors) => { let _ = diagnostics_channel - .send(DiagnosticMessage::Errors { - diagnostics: errors.0, - current_file: None, - }) + .send(DiagnosticMessage::Errors(errors.0)) .await; + let vscode = lsp_data.is_vscode; lsp_data = errors.1; + lsp_data.is_vscode = vscode; lsp_data.config = config.clone(); lsp_data.main_channel = Some(lsp_channel.clone()); let _ = sender.send(true); @@ -141,7 +136,6 @@ pub fn lsp_task( let msg = err.to_string(); client.log_message(MessageType::INFO, msg).await; let _ = sender.send(false); - break; } } } @@ -157,7 +151,7 @@ pub fn lsp_task( LspMessage::Completion(params, sender) => { let position = params.text_document_position.position; let uri = params.text_document_position.text_document.uri.clone(); - let completion = lsp_data.completion(params, can_complete); + let completion = lsp_data.completion(params); let mut items = None; if let Some(completion) = completion { @@ -190,18 +184,67 @@ pub fn lsp_task( items = Some(CompletionResponse::Array(templates)); } } + CompletionType::Snippets { range } => { + let mut filtered = vec![]; + for snippet in snippets.iter() { + let mut snippet = snippet.clone(); + if let Some(CompletionTextEdit::Edit(TextEdit { + new_text, + .. + })) = snippet.text_edit + { + if !lsp_data.is_vscode { + snippet.text_edit = + Some(CompletionTextEdit::InsertAndReplace( + InsertReplaceEdit { + new_text, + insert: range, + replace: range, + }, + )); + } else { + snippet.text_edit = None; + } + } + filtered.push(snippet); + } + + if !filtered.is_empty() { + items = Some(CompletionResponse::Array(filtered)); + } + } }; } let _ = sender.send(items); } LspMessage::Hover(params, sender) => { + let uri = params + .text_document_position_params + .text_document + .uri + .clone(); let mut res = None; if let Some(hover) = lsp_data.hover(params) { - let filter = filters.iter().find(|name| name.name == hover); - if let Some(filter) = filter { + if hover.1 { + let filter = filters + .iter() + .find(|name| name.name == hover.0.name && hover.1); + if let Some(filter) = filter { + let markup_content = MarkupContent { + kind: MarkupKind::Markdown, + value: filter.desc.to_string(), + }; + let hover_contents = HoverContents::Markup(markup_content); + let hover = Hover { + contents: hover_contents, + range: None, + }; + res = Some(hover); + } + } else if let Some(data_type) = lsp_data.data_type(uri, hover.0) { let markup_content = MarkupContent { kind: MarkupKind::Markdown, - value: filter.desc.to_string(), + value: data_type.completion_detail().to_owned(), }; let hover_contents = HoverContents::Markup(markup_content); let hover = Hover { @@ -238,6 +281,23 @@ pub fn lsp_task( let _ = diagnostics_channel.send(errors).await; } } + LspMessage::DocumentSymbol(params, sender) => { + if let Some(symbols) = lsp_data.document_symbols(params) { + let _ = sender.send(Some(symbols)); + } + } + LspMessage::DidChangeConfiguration(params) => { + let (sender, _) = oneshot::channel(); + if let Ok(c) = serde_json::from_value(params.settings) { + config = c; + config.user_defined = true; + } + + if !config.user_defined { + continue; + } + let _ = lsp_channel.send(LspMessage::Initialized(sender)).await; + } } } }); @@ -263,4 +323,9 @@ pub enum LspMessage { oneshot::Sender>, ), ExecuteCommand(ExecuteCommandParams, oneshot::Sender>), + DocumentSymbol( + DocumentSymbolParams, + oneshot::Sender>, + ), + DidChangeConfiguration(DidChangeConfigurationParams), } diff --git a/jinja-lsp/src/config.rs b/jinja-lsp/src/config.rs index db5476d..6939250 100644 --- a/jinja-lsp/src/config.rs +++ b/jinja-lsp/src/config.rs @@ -1,10 +1,11 @@ use std::{collections::HashMap, path::Path}; -use jinja_lsp_queries::tree_builder::{JinjaDiagnostic, JinjaVariable, LangType}; +use jinja_lsp_queries::tree_builder::LangType; use serde::{Deserialize, Serialize}; +use tower_lsp::lsp_types::Diagnostic; use walkdir::WalkDir; -use crate::lsp_files::LspFiles; +use crate::lsp_files2::LspFiles; /// Jinja configuration /// `templates` can be absolute and relative path @@ -35,10 +36,7 @@ impl JinjaConfig { } } -pub type InitLsp = ( - HashMap>, - LspFiles, -); +pub type InitLsp = (HashMap>, LspFiles); pub fn walkdir(config: &JinjaConfig) -> anyhow::Result { let mut all = vec![config.templates.clone()]; @@ -61,6 +59,7 @@ pub fn walkdir(config: &JinjaConfig) -> anyhow::Result { } } + lsp_files.config = config.clone(); lsp_files.read_trees(&mut diags); Ok((diags, lsp_files)) } diff --git a/jinja-lsp/src/lsp_files.rs b/jinja-lsp/src/lsp_files.rs deleted file mode 100644 index 58c04cb..0000000 --- a/jinja-lsp/src/lsp_files.rs +++ /dev/null @@ -1,625 +0,0 @@ -use jinja_lsp_queries::tree_builder::{DataType, JinjaDiagnostic, JinjaVariable, LangType}; -use std::{collections::HashMap, fs::read_to_string, path::Path, time::Duration}; -use tokio::{sync::mpsc, task::JoinHandle, time::sleep}; -use tower_lsp::lsp_types::{ - CompletionItemKind, CompletionTextEdit, DidOpenTextDocumentParams, TextDocumentIdentifier, - TextEdit, -}; - -use jinja_lsp_queries::{ - capturer::{ - included::IncludeCapturer, - init::JinjaInitCapturer, - object::{CompletionType, JinjaObjectCapturer}, - rust::RustCapturer, - }, - lsp_helper::search_errors, - parsers::Parsers, - queries::{query_props, Queries}, - to_input_edit::{to_position, to_position2, ToInputEdit}, -}; -use ropey::Rope; - -use tower_lsp::lsp_types::{ - CodeActionParams, CompletionContext, CompletionItem, CompletionParams, CompletionTriggerKind, - DidChangeTextDocumentParams, DidSaveTextDocumentParams, GotoDefinitionParams, - GotoDefinitionResponse, HoverParams, Location, Position, Range, Url, -}; -use tree_sitter::{InputEdit, Point, Tree}; - -use crate::{ - channels::{diagnostics::DiagnosticMessage, lsp::LspMessage}, - config::JinjaConfig, -}; - -pub struct LspFiles { - trees: HashMap>, - documents: HashMap, - pub parsers: Parsers, - pub variables: HashMap>, - pub queries: Queries, - pub config: JinjaConfig, - pub diagnostics_task: JoinHandle<()>, - pub main_channel: Option>, -} - -impl LspFiles { - pub fn read_file(&mut self, path: &&Path, lang_type: LangType) -> Option<()> { - if let Ok(name) = std::fs::canonicalize(path) { - let name = name.to_str()?; - let file_content = read_to_string(name).ok()?; - let rope = Rope::from_str(&file_content); - let name = format!("file://{}", name); - let adding = name.clone(); - self.delete_variables(&name); - self.documents.insert(name.to_string(), rope); - self.add_tree(&name, lang_type, &file_content); - self.add_variables(&adding, lang_type, &file_content); - } - None - } - - pub fn add_tree( - &mut self, - file_name: &str, - lang_type: LangType, - file_content: &str, - ) -> Option<()> { - let trees = self.trees.get_mut(&lang_type)?; - let old_tree = trees.get_mut(&file_name.to_string()); - match old_tree { - Some(old_tree) => { - let new_tree = self - .parsers - .parse(lang_type, file_content, Some(old_tree))?; - trees.insert(file_name.to_string(), new_tree); - } - None => { - // tree doesn't exist, first insertion - let new_tree = self.parsers.parse(lang_type, file_content, None)?; - trees.insert(file_name.to_string(), new_tree); - } - }; - None - } - - fn delete_variables(&mut self, name: &str) -> Option<()> { - self.variables.get_mut(name)?.clear(); - Some(()) - } - - fn add_variables(&mut self, name: &str, lang_type: LangType, file_content: &str) -> Option<()> { - let trees = self.trees.get(&lang_type).unwrap(); - let tree = trees.get(name)?; - let trigger_point = Point::new(0, 0); - let closest_node = tree.root_node(); - match lang_type { - LangType::Backend => { - let query = &self.queries.rust_idents; - let capturer = RustCapturer::default(); - let mut variables = vec![]; - let capturer = query_props( - closest_node, - file_content, - trigger_point, - query, - true, - capturer, - ); - - for variable in capturer.variables() { - variables.push(JinjaVariable::new( - &variable.0, - variable.1, - DataType::BackendVariable, - )); - } - for macros in capturer.macros() { - for variable in macros.1.variables() { - variables.push(JinjaVariable::new( - variable.0, - *variable.1, - DataType::BackendVariable, - )); - } - } - self.variables.insert(name.to_string(), variables); - } - LangType::Template => { - let query = &self.queries.jinja_init; - let capturer = JinjaInitCapturer::default(); - let capturer = query_props( - closest_node, - file_content, - trigger_point, - query, - true, - capturer, - ); - self.variables.insert(name.to_string(), capturer.to_vec()); - } - } - Some(()) - } - - pub fn input_edit( - &mut self, - file: &String, - code: String, - input_edit: InputEdit, - lang_type: Option, - ) -> Option<()> { - let lang_type = lang_type?; - let trees = self.trees.get_mut(&lang_type)?; - let old_tree = trees.get_mut(file)?; - old_tree.edit(&input_edit); - let new_tree = self.parsers.parse(lang_type, &code, Some(old_tree))?; - let trees = self.trees.get_mut(&lang_type)?; - trees.insert(file.to_string(), new_tree); - None - } - - pub fn read_tree(&self, name: &str) -> Option> { - let rope = self.documents.get(name)?; - let mut writter = FileWriter::default(); - let _ = rope.write_to(&mut writter); - let content = writter.content; - let trees = self.trees.get(&LangType::Template)?; - let tree = trees.get(name)?; - let closest_node = tree.root_node(); - search_errors( - closest_node, - &content, - &self.queries, - &self.variables, - &name.to_string(), - &self.config.templates, - ) - } - - pub fn did_change(&mut self, params: DidChangeTextDocumentParams) -> Option<()> { - let uri = params.text_document.uri.to_string(); - let rope = self.documents.get_mut(&uri)?; - let lang_type = self.config.file_ext(&Path::new(&uri)); - let mut changes = vec![]; - for change in params.content_changes { - let range = change.range?; - let input_edit = rope.to_input_edit(range, &change.text); - if change.text.is_empty() { - let start = rope.to_byte(range.start); - let end = rope.to_byte(range.end); - if start <= end { - rope.remove(start..end); - } else { - rope.remove(end..start); - } - } else { - let start = rope.to_byte(range.start); - rope.insert(start, &change.text); - } - let mut w = FileWriter::default(); - let _ = rope.write_to(&mut w); - changes.push((w.content, input_edit)); - } - for change in changes { - self.input_edit(&uri, change.0, change.1, lang_type); - } - let param = DidSaveTextDocumentParams { - text_document: TextDocumentIdentifier::new(params.text_document.uri), - text: None, - }; - self.diagnostics_task.abort(); - let channel = self.main_channel.clone(); - self.diagnostics_task = tokio::spawn(async move { - sleep(Duration::from_millis(200)).await; - if let Some(channel) = channel { - let _ = channel.send(LspMessage::DidSave(param)).await; - } - }); - None - } - - pub fn did_save(&mut self, params: DidSaveTextDocumentParams) -> Option { - let uri = params.text_document.uri.as_str(); - let path = Path::new(&uri); - let lang_type = self.config.file_ext(&path)?; - let doc = self.documents.get(uri)?; - let mut contents = FileWriter::default(); - let _ = doc.write_to(&mut contents); - let content = contents.content; - self.delete_variables(uri); - self.add_variables(uri, lang_type, &content); - let mut hm = HashMap::new(); - let v = self.read_tree(uri); - if let Some(v) = v { - hm.insert(uri.to_owned(), v); - } else { - hm.insert(uri.to_owned(), vec![]); - } - let message = DiagnosticMessage::Errors { - diagnostics: hm, - current_file: Some(uri.to_owned()), - }; - Some(message) - } - - pub fn completion( - &self, - params: CompletionParams, - can_complete2: bool, - ) -> Option { - let can_complete = { - matches!( - params.context, - Some(CompletionContext { - trigger_kind: CompletionTriggerKind::TRIGGER_CHARACTER, - .. - }) | Some(CompletionContext { - trigger_kind: CompletionTriggerKind::INVOKED, - .. - }) - ) - }; - - if !can_complete { - let can_complete = can_complete2; - if !can_complete { - return None; - } - } - - let uri = params.text_document_position.text_document.uri.to_string(); - let row = params.text_document_position.position.line; - let column = params.text_document_position.position.character; - let point = Point::new(row as usize, column as usize); - let ext = self.config.file_ext(&Path::new(&uri))?; - if ext != LangType::Template { - return None; - } - let trees = self.trees.get(&LangType::Template)?; - let tree = trees.get(&uri)?; - let closest_node = tree.root_node(); - let query = &self.queries.jinja_idents; - let capturer = JinjaObjectCapturer::default(); - let doc = self.documents.get(&uri)?; - let mut writter = FileWriter::default(); - let _ = doc.write_to(&mut writter); - let props = query_props( - closest_node, - &writter.content, - point, - query, - false, - capturer, - ); - if let Some(completion) = props.completion(point) { - return Some(completion); - } - let query = &self.queries.jinja_imports; - let capturer = IncludeCapturer::default(); - let props = query_props( - closest_node, - &writter.content, - point, - query, - false, - capturer, - ); - props.completion(point) - } - - pub fn hover(&self, params: HoverParams) -> Option { - let uri = ¶ms - .text_document_position_params - .text_document - .uri - .clone(); - let lang_type = self.config.file_ext(&Path::new(uri.as_str())); - let can_hover = lang_type.map_or(false, |lang_type| lang_type == LangType::Template); - if !can_hover { - return None; - } - - let uri = params - .text_document_position_params - .text_document - .uri - .to_string(); - let row = params.text_document_position_params.position.line; - let column = params.text_document_position_params.position.character; - let point = Point::new(row as usize, column as usize); - let trees = self.trees.get(&LangType::Template)?; - let tree = trees.get(&uri)?; - let closest_node = tree.root_node(); - let query = &self.queries.jinja_idents; - let capturer = JinjaObjectCapturer::default(); - let doc = self.documents.get(&uri)?; - let mut writter = FileWriter::default(); - let _ = doc.write_to(&mut writter); - let props = query_props( - closest_node, - &writter.content, - point, - query, - false, - capturer, - ); - if props.is_hover(point) { - let id = props.get_last_id()?; - return Some(id); - } - None - } - - pub fn goto_definition(&self, params: GotoDefinitionParams) -> Option { - let uri = params - .text_document_position_params - .text_document - .uri - .to_string(); - let uri2 = params - .text_document_position_params - .text_document - .uri - .clone(); - let row = params.text_document_position_params.position.line; - let column = params.text_document_position_params.position.character; - let point = Point::new(row as usize, column as usize); - let trees = self.trees.get(&LangType::Template)?; - let tree = trees.get(&uri)?; - let closest_node = tree.root_node(); - let mut current_ident = String::new(); - - let query = &self.queries.jinja_idents; - let capturer = JinjaObjectCapturer::default(); - let doc = self.documents.get(&uri)?; - let mut writter = FileWriter::default(); - let _ = doc.write_to(&mut writter); - let props = query_props( - closest_node, - &writter.content, - point, - query, - false, - capturer, - ); - let mut res = props.is_ident(point).and_then(|ident| { - current_ident = ident.to_string(); - let variables = self.variables.get(&uri)?; - let max = variables - .iter() - .filter(|item| item.name == ident && item.location.0 <= point) - .max()?; - let (start, end) = to_position(max); - let range = Range::new(start, end); - Some(GotoDefinitionResponse::Scalar(Location { - uri: uri2.clone(), - range, - })) - }); - res.is_none().then(|| -> Option<()> { - let query = &self.queries.jinja_imports; - let capturer = IncludeCapturer::default(); - let doc = self.documents.get(&uri)?; - let mut writter = FileWriter::default(); - let _ = doc.write_to(&mut writter); - let props = query_props( - closest_node, - &writter.content, - point, - query, - false, - capturer, - ); - if let Some(last) = props.in_template(point) { - let uri = last.is_template(&self.config.templates)?; - let start = to_position2(Point::new(0, 0)); - let end = to_position2(Point::new(0, 0)); - let range = Range::new(start, end); - let location = Location { uri, range }; - res = Some(GotoDefinitionResponse::Scalar(location)); - None - } else { - let mut all: Vec = vec![]; - for i in &self.variables { - let idents = i.1.iter().filter(|item| item.name == current_ident); - for id in idents { - let uri = Url::parse(i.0).unwrap(); - let (start, end) = to_position(id); - let range = Range::new(start, end); - let location = Location { uri, range }; - all.push(location); - } - } - res = Some(GotoDefinitionResponse::Array(all)); - None - } - }); - res - } - - pub fn code_action(&self, action_params: CodeActionParams) -> Option { - let uri = action_params.text_document.uri.to_string(); - let lang_type = self.config.file_ext(&Path::new(&uri)); - let can_def = lang_type.map_or(false, |lang_type| lang_type == LangType::Template); - if !can_def { - return None; - } - let row = action_params.range.start.line; - let column = action_params.range.start.character; - let point = Point::new(row as usize, column as usize); - let trees = self.trees.get(&LangType::Template)?; - let tree = trees.get(&uri)?; - let closest_node = tree.root_node(); - let _current_ident = String::new(); - let query = &self.queries.jinja_idents; - let capturer = JinjaObjectCapturer::default(); - let doc = self.documents.get(&uri)?; - let mut writter = FileWriter::default(); - let _ = doc.write_to(&mut writter); - let props = query_props( - closest_node, - &writter.content, - point, - query, - false, - capturer, - ); - Some(props.in_expr(point)) - } - - pub fn read_trees(&self, diags: &mut HashMap>) { - for tree in self.trees.get(&LangType::Template).unwrap() { - let errors = self.read_tree(tree.0); - if let Some(errors) = errors { - diags.insert(String::from(tree.0), errors); - } - } - } - - pub fn read_variables(&self, uri: &Url, position: Position) -> Option> { - let start = position.line as usize; - let end = position.character as usize; - let position = Point::new(start, end); - let uri = &uri.to_string(); - let variables = self.variables.get(uri)?; - let mut items = vec![]; - for variable in variables.iter() { - if position < variable.location.1 { - continue; - } - items.push(CompletionItem { - label: variable.name.to_string(), - detail: Some(completion_detail(variable.data_type).to_string()), - kind: Some(completion_kind(variable.data_type)), - ..Default::default() - }); - } - for file in self.variables.iter() { - for variable in file.1 { - if variable.data_type == DataType::BackendVariable { - items.push(CompletionItem { - label: variable.name.to_string(), - detail: Some(completion_detail(variable.data_type).to_string()), - kind: Some(completion_kind(variable.data_type)), - ..Default::default() - }); - } - } - } - if !items.is_empty() { - return Some(items); - } - None - } - - pub fn read_templates(&self, mut prefix: String, range: Range) -> Option> { - let all_templates = self.trees.get(&LangType::Template)?; - if prefix.is_empty() { - prefix = String::from("file:///"); - } - let templates = all_templates - .keys() - .filter(|template| template.contains(&prefix)); - let mut abc = vec![]; - for template in templates { - let c = &self.config.templates.replace('.', ""); - let mut parts = template.split(c); - parts.next(); - let label = parts.next()?.replacen('/', "", 1); - let new_text = format!("\"{label}\""); - let text_edit = CompletionTextEdit::Edit(TextEdit::new(range, new_text)); - let item = CompletionItem { - label, - detail: Some("Jinja template".to_string()), - kind: Some(CompletionItemKind::FILE), - text_edit: Some(text_edit), - ..Default::default() - }; - abc.push(item); - } - - Some(abc) - } - - pub fn did_open(&mut self, params: DidOpenTextDocumentParams) -> Option { - let name = params.text_document.uri.as_str(); - let lang_type = self.config.file_ext(&Path::new(name))?; - let file_content = params.text_document.text; - let rope = Rope::from_str(&file_content); - self.delete_variables(name); - self.documents.insert(name.to_string(), rope); - self.add_tree(name, lang_type, &file_content); - self.add_variables(name, lang_type, &file_content); - let diagnostics = self.read_tree(name)?; - let mut hm = HashMap::new(); - hm.insert(name.to_owned(), diagnostics); - let msg = DiagnosticMessage::Errors { - diagnostics: hm, - current_file: Some(name.to_owned()), - }; - Some(msg) - } -} - -impl Default for LspFiles { - fn default() -> Self { - let mut trees = HashMap::new(); - trees.insert(LangType::Template, HashMap::new()); - trees.insert(LangType::Backend, HashMap::new()); - let diagnostics_task = tokio::spawn(async move {}); - let main_channel = None; - Self { - trees, - parsers: Parsers::default(), - variables: HashMap::new(), - queries: Queries::default(), - documents: HashMap::new(), - config: JinjaConfig::default(), - diagnostics_task, - main_channel, - } - } -} - -#[derive(Default, Debug)] -pub struct FileWriter { - pub content: String, -} - -impl std::io::Write for FileWriter { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - if let Ok(b) = std::str::from_utf8(buf) { - self.content.push_str(b); - } - Ok(buf.len()) - } - - fn flush(&mut self) -> std::io::Result<()> { - Ok(()) - } -} - -pub fn completion_kind(variable_type: DataType) -> CompletionItemKind { - match variable_type { - DataType::Macro => CompletionItemKind::FUNCTION, - DataType::MacroParameter => CompletionItemKind::FIELD, - DataType::Variable => CompletionItemKind::VARIABLE, - DataType::BackendVariable => CompletionItemKind::VARIABLE, - DataType::WithVariable => CompletionItemKind::VARIABLE, - DataType::Block => CompletionItemKind::UNIT, - DataType::Template => CompletionItemKind::FILE, - } -} - -pub fn completion_detail(variable_type: DataType) -> &'static str { - match variable_type { - DataType::Macro => "Macro from this file", - DataType::MacroParameter => "Macro parameter from this file", - DataType::Variable => "Variable from this file", - DataType::BackendVariable => "Backend variable.", - DataType::WithVariable => "With variable from this file", - DataType::Block => "Block from this file", - DataType::Template => "Template", - } -} diff --git a/jinja-lsp/src/lsp_files2.rs b/jinja-lsp/src/lsp_files2.rs new file mode 100644 index 0000000..094c0cf --- /dev/null +++ b/jinja-lsp/src/lsp_files2.rs @@ -0,0 +1,665 @@ +use jinja_lsp_queries::{ + lsp_helper::search_errors, + search::{ + completion_start, definition::definition_query, objects::objects_query, queries::Queries, + rust_identifiers::rust_definition_query, rust_template_completion::rust_templates_query, + snippets_completion::snippets_query, templates::templates_query, to_range, Identifier, + IdentifierType, + }, + tree_builder::LangType, +}; +use std::{ + collections::{HashMap, HashSet}, + fs::read_to_string, + path::Path, + time::Duration, +}; +use tokio::{sync::mpsc, task::JoinHandle, time::sleep}; +use tower_lsp::lsp_types::{ + CompletionItemKind, CompletionTextEdit, Diagnostic, DidOpenTextDocumentParams, DocumentSymbol, + DocumentSymbolResponse, TextDocumentIdentifier, TextEdit, +}; + +use jinja_lsp_queries::{ + parsers::Parsers, + search::objects::CompletionType, + to_input_edit::{to_position2, ToInputEdit}, +}; +use ropey::Rope; + +use tower_lsp::lsp_types::{ + CodeActionParams, CompletionContext, CompletionItem, CompletionParams, CompletionTriggerKind, + DidChangeTextDocumentParams, DidSaveTextDocumentParams, GotoDefinitionParams, + GotoDefinitionResponse, HoverParams, Location, Position, Range, Url, +}; +use tree_sitter::{InputEdit, Point, Tree}; + +use crate::{ + channels::{diagnostics::DiagnosticMessage, lsp::LspMessage}, + config::JinjaConfig, +}; + +pub struct LspFiles { + trees: HashMap>, + documents: HashMap, + pub parsers: Parsers, + pub queries: Queries, + pub config: JinjaConfig, + pub diagnostics_task: JoinHandle<()>, + pub main_channel: Option>, + pub variables: HashMap>, + pub is_vscode: bool, +} + +impl LspFiles { + pub fn read_file(&mut self, path: &&Path, lang_type: LangType) -> Option<()> { + if let Ok(name) = std::fs::canonicalize(path) { + let name = name.to_str()?; + let file_content = read_to_string(name).ok()?; + let rope = Rope::from_str(&file_content); + let name = format!("file://{}", name); + let adding = name.clone(); + self.documents.insert(name.to_string(), rope); + self.add_tree(&name, lang_type, &file_content); + self.add_variables(&adding, lang_type, &file_content); + } + None + } + + fn add_variables(&mut self, name: &str, lang_type: LangType, file_content: &str) -> Option<()> { + let trees = self.trees.get(&lang_type).unwrap(); + let tree = trees.get(name)?; + let trigger_point = Point::new(0, 0); + match lang_type { + LangType::Backend => { + let mut variables = vec![]; + let query_defs = &self.queries.rust_definitions; + let query_templates = &self.queries.rust_templates; + let mut ids = + rust_definition_query(query_defs, tree, trigger_point, file_content, true) + .show(); + let mut templates = + rust_templates_query(query_templates, tree, trigger_point, file_content, true) + .collect(); + variables.append(&mut ids); + variables.append(&mut templates); + self.variables.insert(String::from(name), variables); + } + LangType::Template => { + let mut variables = vec![]; + let query_snippets = &self.queries.jinja_snippets; + let snippets = + snippets_query(query_snippets, tree, trigger_point, file_content, true); + if snippets.is_error { + return None; + } + let query_defs = &self.queries.jinja_definitions; + let mut definitions = + definition_query(query_defs, tree, trigger_point, file_content, true) + .identifiers(); + variables.append(&mut definitions); + self.variables.insert(String::from(name), variables); + } + } + Some(()) + } + + pub fn add_tree( + &mut self, + file_name: &str, + lang_type: LangType, + file_content: &str, + ) -> Option<()> { + let trees = self.trees.get_mut(&lang_type)?; + let old_tree = trees.get_mut(&file_name.to_string()); + match old_tree { + Some(old_tree) => { + let new_tree = self + .parsers + .parse(lang_type, file_content, Some(old_tree))?; + trees.insert(file_name.to_string(), new_tree); + } + None => { + // tree doesn't exist, first insertion + let new_tree = self.parsers.parse(lang_type, file_content, None)?; + trees.insert(file_name.to_string(), new_tree); + } + }; + None + } + + pub fn input_edit( + &mut self, + file: &String, + code: String, + input_edit: InputEdit, + lang_type: Option, + ) -> Option<()> { + let lang_type = lang_type?; + let trees = self.trees.get_mut(&lang_type)?; + let old_tree = trees.get_mut(file)?; + old_tree.edit(&input_edit); + let new_tree = self.parsers.parse(lang_type, &code, Some(old_tree))?; + let trees = self.trees.get_mut(&lang_type)?; + trees.insert(file.to_string(), new_tree); + None + } + + pub fn did_change(&mut self, params: DidChangeTextDocumentParams) -> Option<()> { + let uri = params.text_document.uri.to_string(); + let rope = self.documents.get_mut(&uri)?; + let lang_type = self.config.file_ext(&Path::new(&uri)); + let mut changes = vec![]; + for change in params.content_changes { + let range = change.range?; + let input_edit = rope.to_input_edit(range, &change.text); + let start = rope.to_byte(range.start); + let end = rope.to_byte(range.end); + if start <= end { + rope.remove(start..end); + } else { + rope.remove(end..start); + } + if !change.text.is_empty() { + rope.insert(start, &change.text); + } + let mut w = FileContent::default(); + let _ = rope.write_to(&mut w); + changes.push((w.content, input_edit)); + } + for change in changes { + self.input_edit(&uri, change.0, change.1, lang_type); + } + let param = DidSaveTextDocumentParams { + text_document: TextDocumentIdentifier::new(params.text_document.uri), + text: None, + }; + self.diagnostics_task.abort(); + let channel = self.main_channel.clone(); + self.diagnostics_task = tokio::spawn(async move { + sleep(Duration::from_millis(200)).await; + if let Some(channel) = channel { + let _ = channel.send(LspMessage::DidSave(param)).await; + } + }); + None + } + + pub fn read_tree(&self, name: &str) -> Option> { + let rope = self.documents.get(name)?; + let mut writter = FileContent::default(); + let _ = rope.write_to(&mut writter); + let content = writter.content; + let lang_type = self.config.file_ext(&Path::new(name))?; + let trees = self.trees.get(&lang_type)?; + let tree = trees.get(name)?; + search_errors( + tree, + &content, + &self.queries, + &self.variables, + &name.to_string(), + &self.config.templates, + lang_type, + ) + } + + pub fn did_save(&mut self, params: DidSaveTextDocumentParams) -> Option { + let uri = params.text_document.uri.as_str(); + let path = Path::new(&uri); + let lang_type = self.config.file_ext(&path)?; + let doc = self.documents.get(uri)?; + let mut contents = FileContent::default(); + let _ = doc.write_to(&mut contents); + let content = contents.content; + self.delete_variables(uri); + self.add_variables(uri, lang_type, &content); + let mut hm = HashMap::new(); + let v = self.read_tree(uri); + if let Some(v) = v { + hm.insert(uri.to_owned(), v); + } else { + hm.insert(uri.to_owned(), vec![]); + } + let message = DiagnosticMessage::Errors(hm); + Some(message) + } + + pub fn completion(&self, params: CompletionParams) -> Option { + let can_complete = { + matches!( + params.context, + Some(CompletionContext { + trigger_kind: CompletionTriggerKind::TRIGGER_CHARACTER, + .. + }) | Some(CompletionContext { + trigger_kind: CompletionTriggerKind::INVOKED, + .. + }) + ) + }; + + if !can_complete { + return None; + } + + let uri = params.text_document_position.text_document.uri.to_string(); + let row = params.text_document_position.position.line; + let column = params.text_document_position.position.character; + let point = Point::new(row as usize, column as usize); + let ext = self.config.file_ext(&Path::new(&uri))?; + let trees = self.trees.get(&ext)?; + let tree = trees.get(&uri)?; + let doc = self.documents.get(&uri)?; + let mut writter = FileContent::default(); + let _ = doc.write_to(&mut writter); + match ext { + LangType::Template => { + let query = &self.queries.jinja_snippets; + let snippets = snippets_query(query, tree, point, &writter.content, false); + if snippets.to_complete(point).is_some() { + let start = to_position2(point); + let mut end = to_position2(point); + end.character += 1; + let range = Range::new(start, end); + return Some(CompletionType::Snippets { range }); + } + let query = &self.queries.jinja_objects; + let objects = objects_query(query, tree, point, &writter.content, false); + if let Some(completion) = objects.completion(point) { + return Some(completion); + } + let query = &self.queries.jinja_imports; + let query = templates_query(query, tree, point, &writter.content, false); + let identifier = query.in_template(point)?.get_identifier(point)?; + let start = completion_start(point, identifier)?; + let range = to_range((identifier.start, identifier.end)); + Some(CompletionType::IncludedTemplate { + name: start.to_owned(), + range, + }) + } + LangType::Backend => { + let rust_templates = rust_templates_query( + &self.queries.rust_templates, + tree, + point, + &writter.content, + false, + ); + let identifier = rust_templates.in_template(point)?; + let start = completion_start(point, identifier)?; + let range = to_range((identifier.start, identifier.end)); + Some(CompletionType::IncludedTemplate { + name: start.to_owned(), + range, + }) + } + } + } + + fn delete_variables(&mut self, uri: &str) -> Option<()> { + self.variables.get_mut(uri)?.clear(); + Some(()) + } + + pub fn hover(&self, params: HoverParams) -> Option<(Identifier, bool)> { + let uri = ¶ms + .text_document_position_params + .text_document + .uri + .clone(); + let lang_type = self.config.file_ext(&Path::new(uri.as_str())); + let can_hover = lang_type.map_or(false, |lang_type| lang_type == LangType::Template); + if !can_hover { + return None; + } + + let uri = params + .text_document_position_params + .text_document + .uri + .to_string(); + let row = params.text_document_position_params.position.line; + let column = params.text_document_position_params.position.character; + let trigger_point = Point::new(row as usize, column as usize); + let trees = self.trees.get(&LangType::Template)?; + let tree = trees.get(&uri)?; + let query = &self.queries.jinja_objects; + let doc = self.documents.get(&uri)?; + let mut writter = FileWriter::default(); + let _ = doc.write_to(&mut writter); + let objects = objects_query(query, tree, trigger_point, &writter.content, false); + if objects.is_hover(trigger_point) { + let object = objects.get_last_id()?; + if object.is_filter { + return Some((Identifier::from(object), true)); + } else { + return Some((Identifier::from(object), false)); + } + } + // else if objects.is_ident(point) { + + // } + None + } + + pub fn goto_definition(&self, params: GotoDefinitionParams) -> Option { + let uri = params + .text_document_position_params + .text_document + .uri + .to_string(); + let uri2 = params + .text_document_position_params + .text_document + .uri + .clone(); + let lang_type = self.config.file_ext(&Path::new(&uri))?; + let trees = self.trees.get(&lang_type)?; + let tree = trees.get(&uri)?; + let row = params.text_document_position_params.position.line; + let column = params.text_document_position_params.position.character; + let point = Point::new(row as usize, column as usize); + let doc = self.documents.get(&uri)?; + let mut writter = FileWriter::default(); + let _ = doc.write_to(&mut writter); + + let mut current_ident = String::new(); + + match lang_type { + LangType::Template => { + let query = &self.queries.jinja_objects; + let objects = objects_query(query, tree, point, &writter.content, false); + let mut res = objects.is_ident(point).and_then(|ident| { + current_ident = ident.to_owned(); + let variables = self.variables.get(&uri)?; + let max = variables + .iter() + .filter(|item| { + item.name == ident && item.start <= point && point <= item.scope_ends.1 + }) + .max()?; + let (start, end) = (to_position2(max.start), to_position2(max.end)); + let range = Range::new(start, end); + Some(GotoDefinitionResponse::Scalar(Location { + uri: uri2.clone(), + range, + })) + }); + res.is_none().then(|| -> Option<()> { + let query = &self.queries.jinja_imports; + let query = templates_query(query, tree, point, &writter.content, false); + let identifier = query.in_template(point)?.get_identifier(point)?; + let dir = &self.config.templates; + let path = format!("{dir}/{}", identifier.name); + let buffer = std::fs::canonicalize(path).ok()?; + let url = format!("file://{}", buffer.to_str()?); + let url = Url::parse(&url).ok()?; + let start = to_position2(identifier.start); + let end = to_position2(identifier.end); + let range = Range::new(start, end); + let location = Location::new(url, range); + res = Some(GotoDefinitionResponse::Scalar(location)); + None + }); + res.is_none().then(|| -> Option<()> { + let mut all: Vec = vec![]; + for file in &self.variables { + if file.0 == &uri { + continue; + } + let variables = file.1.iter().filter(|item| item.name == current_ident); + for variable in variables { + let uri = Url::parse(file.0).unwrap(); + let start = to_position2(variable.start); + let end = to_position2(variable.end); + let range = Range::new(start, end); + let location = Location::new(uri, range); + all.push(location); + } + } + res = Some(GotoDefinitionResponse::Array(all)); + None + }); + res + } + + LangType::Backend => { + let query = &self.queries.rust_templates; + let templates = rust_templates_query(query, tree, point, &writter.content, false); + let template = templates.in_template(point)?; + let dir = &self.config.templates; + let path = format!("{dir}/{}", template.name); + let buffer = std::fs::canonicalize(path).ok()?; + let url = format!("file://{}", buffer.to_str()?); + let url = Url::parse(&url).ok()?; + let start = to_position2(template.start); + let end = to_position2(template.end); + let range = Range::new(start, end); + let location = Location::new(url, range); + Some(GotoDefinitionResponse::Scalar(location)) + } + } + } + + pub fn code_action(&self, action_params: CodeActionParams) -> Option { + let uri = action_params.text_document.uri.to_string(); + let lang_type = self.config.file_ext(&Path::new(&uri)); + let can_def = lang_type.map_or(false, |lang_type| lang_type == LangType::Template); + if !can_def { + return None; + } + let row = action_params.range.start.line; + let column = action_params.range.start.character; + let point = Point::new(row as usize, column as usize); + let trees = self.trees.get(&LangType::Template)?; + let tree = trees.get(&uri)?; + let query = &self.queries.jinja_objects; + let doc = self.documents.get(&uri)?; + let mut writter = FileWriter::default(); + let _ = doc.write_to(&mut writter); + let objects = objects_query(query, tree, point, &writter.content, false); + Some(objects.in_expr(point)) + } + + pub fn read_trees(&self, diags: &mut HashMap>) { + for tree in self.trees.get(&LangType::Template).unwrap() { + let errors = self.read_tree(tree.0); + if let Some(errors) = errors { + diags.insert(String::from(tree.0), errors); + } + } + } + + pub fn read_variables(&self, uri: &Url, position: Position) -> Option> { + let mut items = vec![]; + let start = position.line as usize; + let end = position.character as usize; + let position = Point::new(start, end); + let uri = &uri.to_string(); + let mut names = HashSet::new(); + let this_file = self.variables.get(uri)?; + let this_file = this_file + .iter() + .filter(|variable| { + variable.identifier_type != IdentifierType::TemplateBlock + && variable.identifier_type != IdentifierType::JinjaTemplate + }) + .filter(|variable| { + let bigger = position >= variable.end; + let in_scope = position <= variable.scope_ends.1; + bigger && in_scope + }); + for identifier in this_file { + if !names.contains(&identifier.name) { + names.insert(&identifier.name); + items.push(CompletionItem { + label: identifier.name.to_string(), + detail: Some(identifier.identifier_type.completion_detail().to_owned()), + kind: Some(identifier.identifier_type.completion_kind()), + ..Default::default() // detail: Some() + }); + } + } + for file in self.variables.iter() { + for variable in file.1 { + if variable.identifier_type == IdentifierType::BackendVariable { + items.push(CompletionItem { + label: variable.name.to_string(), + detail: Some(variable.identifier_type.completion_detail().to_owned()), + kind: Some(variable.identifier_type.completion_kind()), + ..Default::default() // detail: Some() + }); + } + } + } + Some(items) + } + + pub fn read_templates(&self, mut prefix: String, range: Range) -> Option> { + let all_templates = self.trees.get(&LangType::Template)?; + if prefix.is_empty() { + prefix = String::from("file:///"); + } + let templates = all_templates + .keys() + .filter(|template| template.contains(&prefix)); + let mut abc = vec![]; + for template in templates { + let c = &self.config.templates.replace('.', ""); + let mut parts = template.split(c); + parts.next(); + let label = parts.next()?.replacen('/', "", 1); + let new_text = format!("\"{label}\""); + let text_edit = { + if self.is_vscode { + None + } else { + Some(CompletionTextEdit::Edit(TextEdit::new(range, new_text))) + } + }; + let item = CompletionItem { + label, + detail: Some("Jinja template".to_string()), + kind: Some(CompletionItemKind::FILE), + text_edit, + ..Default::default() + }; + abc.push(item); + } + + Some(abc) + } + + pub fn did_open(&mut self, params: DidOpenTextDocumentParams) -> Option { + let name = params.text_document.uri.as_str(); + let lang_type = self.config.file_ext(&Path::new(name))?; + let file_content = params.text_document.text; + let rope = Rope::from_str(&file_content); + self.delete_variables(name); + self.documents.insert(name.to_string(), rope); + self.add_tree(name, lang_type, &file_content); + self.add_variables(name, lang_type, &file_content); + let diagnostics = self.read_tree(name)?; + let mut hm = HashMap::new(); + hm.insert(name.to_owned(), diagnostics); + let msg = DiagnosticMessage::Errors(hm); + Some(msg) + } + + pub fn data_type(&self, uri: Url, hover: Identifier) -> Option { + let this_file = self.variables.get(&uri.as_str().to_string())?; + let this_file = this_file + .iter() + .filter(|variable| variable.identifier_type != IdentifierType::TemplateBlock) + .filter(|variable| { + let bigger = hover.start >= variable.end; + let in_scope = hover.start <= variable.scope_ends.1; + let same_name = hover.name == variable.name; + bigger && in_scope && same_name + }) + .max()?; + Some(this_file.identifier_type.clone()) + } + + pub fn document_symbols( + &self, + params: tower_lsp::lsp_types::DocumentSymbolParams, + ) -> Option { + let mut symbols = vec![]; + let variables = self.variables.get(params.text_document.uri.as_str())?; + for variable in variables { + #[allow(deprecated)] + let symbol = DocumentSymbol { + name: variable.name.to_owned(), + detail: None, + kind: variable.identifier_type.symbol_kind(), + range: to_range((variable.start, variable.end)), + selection_range: to_range((variable.start, variable.end)), + children: None, + deprecated: None, + tags: None, + }; + symbols.push(symbol); + } + Some(DocumentSymbolResponse::Nested(symbols)) + } +} + +impl Default for LspFiles { + fn default() -> Self { + let mut trees = HashMap::new(); + trees.insert(LangType::Template, HashMap::new()); + trees.insert(LangType::Backend, HashMap::new()); + let diagnostics_task = tokio::spawn(async move {}); + let main_channel = None; + Self { + trees, + parsers: Parsers::default(), + queries: Queries::default(), + documents: HashMap::new(), + config: JinjaConfig::default(), + diagnostics_task, + main_channel, + variables: HashMap::default(), + is_vscode: false, + } + } +} + +#[derive(Default, Debug)] +pub struct FileContent { + pub content: String, +} + +impl std::io::Write for FileContent { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + if let Ok(b) = std::str::from_utf8(buf) { + self.content.push_str(b); + } + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +#[derive(Default, Debug)] +pub struct FileWriter { + pub content: String, +} + +impl std::io::Write for FileWriter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + if let Ok(b) = std::str::from_utf8(buf) { + self.content.push_str(b); + } + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} diff --git a/jinja-lsp/src/main.rs b/jinja-lsp/src/main.rs index 1f6209f..27a9c8a 100644 --- a/jinja-lsp/src/main.rs +++ b/jinja-lsp/src/main.rs @@ -2,7 +2,7 @@ mod backend; pub mod channels; mod config; mod filter; -pub mod lsp_files; +pub mod lsp_files2; use backend::Backend; use tower_lsp::LspService;