diff --git a/Cargo.lock b/Cargo.lock index 1bf4a21..e98306f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,9 +43,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.19" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" dependencies = [ "anstyle", "anstyle-parse", @@ -73,22 +73,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -132,7 +132,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -190,9 +190,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.30" +version = "1.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e" dependencies = [ "shlex", ] @@ -226,9 +226,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.42" +version = "4.5.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882" +checksum = "1c1f056bae57e3e54c3375c41ff79619ddd13460a17d7438712bd0d83fda4ff8" dependencies = [ "clap_builder", "clap_derive", @@ -236,9 +236,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.42" +version = "4.5.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966" +checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" dependencies = [ "anstream", "anstyle", @@ -299,9 +299,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "darling" -version = "0.21.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a79c4acb1fd5fa3d9304be4c76e031c54d2e92d172a393e24b19a14fe8532fe9" +checksum = "d6b136475da5ef7b6ac596c0e956e37bad51b85b987ff3d5e230e964936736b2" dependencies = [ "darling_core", "darling_macro", @@ -309,9 +309,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.21.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74875de90daf30eb59609910b84d4d368103aaec4c924824c6799b28f77d6a1d" +checksum = "b44ad32f92b75fb438b04b68547e521a548be8acc339a6dacc4a7121488f53e6" dependencies = [ "fnv", "ident_case", @@ -323,9 +323,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.21.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79f8e61677d5df9167cd85265f8e5f64b215cdea3fb55eebc3e622e44c7a146" +checksum = "2b5be8a7a562d315a5b92a630c30cec6bcf663e6673f00fbb69cca66a6f521b9" dependencies = [ "darling_core", "quote", @@ -357,9 +357,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "equivalent" @@ -490,9 +490,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "hashbrown" @@ -502,9 +502,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" [[package]] name = "heck" @@ -549,7 +549,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown 0.15.4", + "hashbrown 0.15.5", ] [[package]] @@ -602,9 +602,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.174" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "lock_api" @@ -712,7 +712,7 @@ dependencies = [ "schemars", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "tracing", "tracing-appender", @@ -782,7 +782,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -820,9 +820,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" dependencies = [ "unicode-ident", ] @@ -887,9 +887,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.15" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags", ] @@ -960,9 +960,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rmcp" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b008b927a85d514699ff304be84c5084598596e6cad4a6f5bc67207715fafe5f" +checksum = "2faf35b7d3c4b7f8c21c45bb014011b32a0ce6444bf6094da04daab01a8c3c34" dependencies = [ "base64", "chrono", @@ -974,7 +974,7 @@ dependencies = [ "schemars", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "tokio-stream", "tokio-util", @@ -983,9 +983,9 @@ dependencies = [ [[package]] name = "rmcp-macros" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7465280d5f73f2c5c99017a04af407b2262455a149f255ad22f2b0b29087695c" +checksum = "ad9720d9d2a943779f1dc3d47fa9072c7eeffaff4e1a82f67eb9f7ea52696091" dependencies = [ "darling", "proc-macro2", @@ -1017,15 +1017,15 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" @@ -1098,9 +1098,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.141" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" dependencies = [ "itoa", "memchr", @@ -1125,18 +1125,18 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] [[package]] name = "slab" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" @@ -1146,12 +1146,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.5.10" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1182,11 +1182,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.14", ] [[package]] @@ -1202,9 +1202,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" dependencies = [ "proc-macro2", "quote", @@ -1253,9 +1253,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.46.1" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes 1.10.1", @@ -1268,7 +1268,7 @@ dependencies = [ "slab", "socket2", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1306,9 +1306,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes 1.10.1", "futures-core", @@ -1527,9 +1527,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.61.1" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ "windows-collections", "windows-core", @@ -1562,12 +1562,13 @@ dependencies = [ [[package]] name = "windows-future" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a1d6bbefcb7b60acd19828e1bc965da6fcf18a7e39490c5f8be71e54a19ba32" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ "windows-core", "windows-link", + "windows-threading", ] [[package]] @@ -1628,20 +1629,20 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets", + "windows-targets 0.53.3", ] [[package]] @@ -1650,14 +1651,40 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", ] [[package]] @@ -1666,48 +1693,96 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "wit-bindgen-rt" version = "0.39.0" diff --git a/Cargo.toml b/Cargo.toml index d56fc18..7f38c7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ tracing-appender = "0.2" clap = { version = "4.5", features = ["derive"] } # MCP Protocol Implementation -rmcp = { version = "0.3.0", features = [ +rmcp = { version = "0.5.0", features = [ "transport-io", "transport-child-process", "client", diff --git a/src/neovim/client.rs b/src/neovim/client.rs index 295ad8e..8a6e6f5 100644 --- a/src/neovim/client.rs +++ b/src/neovim/client.rs @@ -1,11 +1,14 @@ #![allow(rustdoc::invalid_codeblock_attributes)] use std::collections::HashMap; +use std::fmt; use std::path::{Path, PathBuf}; use async_trait::async_trait; use nvim_rs::{Handler, Neovim, create::tokio as create}; use rmpv::Value; +use serde::de::{self, MapAccess, Visitor}; +use serde::{Deserialize, Deserializer}; use tokio::{io::AsyncWrite, net::TcpStream}; use tracing::{debug, info, instrument}; @@ -186,7 +189,7 @@ pub struct TextDocumentIdentifier { } /// Universal identifier for text documents supporting multiple reference types -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, schemars::JsonSchema)] #[serde(rename_all = "snake_case")] pub enum DocumentIdentifier { /// Reference by Neovim buffer ID (for currently open files) @@ -197,6 +200,66 @@ pub enum DocumentIdentifier { AbsolutePath(PathBuf), } +/// Supports both string and struct deserialization for DocumentIdentifier. +/// Compatible with Claude Code when using subscription. +struct DocumentIdentifierVisitor; + +impl<'de> Visitor<'de> for DocumentIdentifierVisitor { + type Value = DocumentIdentifier; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("string or object") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + serde_json::from_str(value) + .map_err(|e| de::Error::custom(format!("Failed to parse JSON string: {}", e))) + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'de>, + { + while let Some(key) = map.next_key::()? { + match key.as_str() { + "buffer_id" => { + let value: u64 = map.next_value()?; + return Ok(DocumentIdentifier::BufferId(value)); + } + "project_relative_path" => { + let value: String = map.next_value()?; + return Ok(DocumentIdentifier::ProjectRelativePath(PathBuf::from( + value, + ))); + } + "absolute_path" => { + let value: String = map.next_value()?; + return Ok(DocumentIdentifier::AbsolutePath(PathBuf::from(value))); + } + _ => { + // Skip unknown keys to be forward compatible + let _: serde::de::IgnoredAny = map.next_value()?; + } + } + } + Err(de::Error::custom( + "Expected one of: buffer_id, project_relative_path, or absolute_path", + )) + } +} + +impl<'de> Deserialize<'de> for DocumentIdentifier { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(DocumentIdentifierVisitor) + } +} + impl DocumentIdentifier { /// Create from buffer ID pub fn from_buffer_id(buffer_id: u64) -> Self { @@ -1716,4 +1779,180 @@ mod tests { assert!(schema_json.contains("project_relative_path")); assert!(schema_json.contains("absolute_path")); } + + #[test] + fn test_document_identifier_string_deserializer_buffer_id() { + // Test deserializing buffer_id from JSON string + let json_str = r#"{"buffer_id": 42}"#; + let result: Result = serde_json::from_str(json_str); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), DocumentIdentifier::BufferId(42)); + + // Test large buffer ID + let json_str = r#"{"buffer_id": 18446744073709551615}"#; + let result: Result = serde_json::from_str(json_str); + assert!(result.is_ok()); + assert_eq!( + result.unwrap(), + DocumentIdentifier::BufferId(18446744073709551615) + ); + } + + #[test] + fn test_document_identifier_string_deserializer_project_path() { + // Test deserializing project_relative_path from JSON string + let json_str = r#"{"project_relative_path": "src/main.rs"}"#; + let result: Result = serde_json::from_str(json_str); + assert!(result.is_ok()); + assert_eq!( + result.unwrap(), + DocumentIdentifier::ProjectRelativePath(PathBuf::from("src/main.rs")) + ); + + // Test with nested path + let json_str = r#"{"project_relative_path": "src/server/tools.rs"}"#; + let result: Result = serde_json::from_str(json_str); + assert!(result.is_ok()); + assert_eq!( + result.unwrap(), + DocumentIdentifier::ProjectRelativePath(PathBuf::from("src/server/tools.rs")) + ); + + // Test with empty path + let json_str = r#"{"project_relative_path": ""}"#; + let result: Result = serde_json::from_str(json_str); + assert!(result.is_ok()); + assert_eq!( + result.unwrap(), + DocumentIdentifier::ProjectRelativePath(PathBuf::from("")) + ); + } + + #[test] + fn test_document_identifier_string_deserializer_absolute_path() { + // Test deserializing absolute_path from JSON string + let json_str = r#"{"absolute_path": "/usr/local/src/main.rs"}"#; + let result: Result = serde_json::from_str(json_str); + assert!(result.is_ok()); + assert_eq!( + result.unwrap(), + DocumentIdentifier::AbsolutePath(PathBuf::from("/usr/local/src/main.rs")) + ); + + // Test Windows-style absolute path + let json_str = r#"{"absolute_path": "C:\\Users\\test\\Documents\\file.rs"}"#; + let result: Result = serde_json::from_str(json_str); + assert!(result.is_ok()); + assert_eq!( + result.unwrap(), + DocumentIdentifier::AbsolutePath(PathBuf::from("C:\\Users\\test\\Documents\\file.rs")) + ); + } + + #[test] + fn test_document_identifier_string_deserializer_via_visitor() { + // Test the custom deserializer's visit_str method by passing JSON as a string value + // This simulates Claude Code's usage pattern where JSON is embedded as string + let json_as_string = r#""{\"buffer_id\": 123}""#; + let result: Result = serde_json::from_str(json_as_string); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), DocumentIdentifier::BufferId(123)); + + let json_as_string = r#""{\"project_relative_path\": \"src/lib.rs\"}""#; + let result: Result = serde_json::from_str(json_as_string); + assert!(result.is_ok()); + assert_eq!( + result.unwrap(), + DocumentIdentifier::ProjectRelativePath(PathBuf::from("src/lib.rs")) + ); + + let json_as_string = r#""{\"absolute_path\": \"/home/user/file.rs\"}""#; + let result: Result = serde_json::from_str(json_as_string); + assert!(result.is_ok()); + assert_eq!( + result.unwrap(), + DocumentIdentifier::AbsolutePath(PathBuf::from("/home/user/file.rs")) + ); + } + + #[test] + fn test_document_identifier_string_deserializer_error_cases() { + // Test invalid JSON string format + let invalid_json = r#"{"invalid_field": 42}"#; + let result: Result = serde_json::from_str(invalid_json); + assert!(result.is_err()); + + // Test empty JSON object + let empty_json = r#"{}"#; + let result: Result = serde_json::from_str(empty_json); + assert!(result.is_err()); + + // Test malformed JSON + let malformed_json = r#"{"buffer_id": invalid}"#; + let result: Result = serde_json::from_str(malformed_json); + assert!(result.is_err()); + + // Test negative buffer_id (should fail for u64) + let negative_json = r#"{"buffer_id": -1}"#; + let result: Result = serde_json::from_str(negative_json); + assert!(result.is_err()); + + // Test string with malformed embedded JSON + let malformed_embedded = r#""{\"buffer_id\": }"#; + let result: Result = serde_json::from_str(malformed_embedded); + assert!(result.is_err()); + } + + #[test] + fn test_document_identifier_string_deserializer_mixed_cases() { + // Test with extra whitespace in JSON + let json_str = r#"{ "buffer_id" : 999 }"#; + let result: Result = serde_json::from_str(json_str); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), DocumentIdentifier::BufferId(999)); + + // Test with Unicode in paths + let json_str = r#"{"project_relative_path": "src/测试.rs"}"#; + let result: Result = serde_json::from_str(json_str); + assert!(result.is_ok()); + assert_eq!( + result.unwrap(), + DocumentIdentifier::ProjectRelativePath(PathBuf::from("src/测试.rs")) + ); + + // Test with special characters in paths + let json_str = r#"{"absolute_path": "/tmp/test with spaces & symbols!.rs"}"#; + let result: Result = serde_json::from_str(json_str); + assert!(result.is_ok()); + assert_eq!( + result.unwrap(), + DocumentIdentifier::AbsolutePath(PathBuf::from("/tmp/test with spaces & symbols!.rs")) + ); + } + + #[test] + fn test_document_identifier_round_trip_serialization() { + // Test round-trip serialization/deserialization for all variants + let test_cases = vec![ + DocumentIdentifier::BufferId(42), + DocumentIdentifier::ProjectRelativePath(PathBuf::from("src/main.rs")), + DocumentIdentifier::AbsolutePath(PathBuf::from("/usr/src/main.rs")), + ]; + + for original in test_cases { + // Serialize to JSON + let json = serde_json::to_string(&original).unwrap(); + + // Deserialize back from JSON + let deserialized: DocumentIdentifier = serde_json::from_str(&json).unwrap(); + + // Verify round-trip equality + assert_eq!(original, deserialized); + + // Test string-embedded JSON deserialization (Claude Code use case) + let json_string = serde_json::to_string(&json).unwrap(); + let from_string: DocumentIdentifier = serde_json::from_str(&json_string).unwrap(); + assert_eq!(original, from_string); + } + } } diff --git a/src/server/integration_tests.rs b/src/server/integration_tests.rs index 61595f4..5a52367 100644 --- a/src/server/integration_tests.rs +++ b/src/server/integration_tests.rs @@ -14,7 +14,7 @@ use crate::test_utils::*; fn extract_connection_id( result: &rmcp::model::CallToolResult, ) -> Result> { - if let Some(content) = result.content.first() { + if let Some(content) = result.content.as_ref().and_then(|c| c.first()) { // The content should be JSON let json_str = match &content.raw { rmcp::model::RawContent::Text(text_content) => &text_content.text, @@ -148,10 +148,10 @@ async fn test_connect_nvim_tcp_tool() -> Result<(), Box> .await?; info!("Connect result: {:#?}", result); - assert!(!result.content.is_empty()); + assert!(!result.content.as_ref().is_none_or(|c| c.is_empty())); // Verify the response contains success message - if let Some(content) = result.content.first() { + if let Some(content) = result.content.as_ref().and_then(|c| c.first()) { if let Some(text) = content.as_text() { assert!(text.text.contains("Connected to Neovim")); assert!(text.text.contains(&ipc_path)); @@ -254,10 +254,10 @@ async fn test_disconnect_nvim_tcp_tool() -> Result<(), Box Result<(), Box> { .await?; info!("List buffers result: {:#?}", result); - assert!(!result.content.is_empty()); + assert!(!result.content.as_ref().is_none_or(|c| c.is_empty())); // Verify the response contains buffer information - if let Some(content) = result.content.first() { + if let Some(content) = result.content.as_ref().and_then(|c| c.first()) { if let Some(text) = content.as_text() { // The response should be JSON with buffer info assert!(text.text.contains("\"id\"")); @@ -414,7 +414,7 @@ async fn test_complete_workflow() -> Result<(), Box> { }) .await?; - assert!(!connect_result.content.is_empty()); + assert!(!connect_result.content.as_ref().is_none_or(|c| c.is_empty())); let connection_id = extract_connection_id(&connect_result)?; info!( "✓ Connected successfully with connection_id: {}", @@ -436,7 +436,7 @@ async fn test_complete_workflow() -> Result<(), Box> { }) .await?; - assert!(!result.content.is_empty()); + assert!(!result.content.as_ref().is_none_or(|c| c.is_empty())); info!("✓ Listed buffers successfully"); // Step 3: Get LSP clients @@ -454,7 +454,7 @@ async fn test_complete_workflow() -> Result<(), Box> { }) .await?; - assert!(!result.content.is_empty()); + assert!(!result.content.as_ref().is_none_or(|c| c.is_empty())); info!("✓ Got LSP clients successfully"); // Step 4: Disconnect @@ -472,7 +472,7 @@ async fn test_complete_workflow() -> Result<(), Box> { }) .await?; - assert!(!result.content.is_empty()); + assert!(!result.content.as_ref().is_none_or(|c| c.is_empty())); info!("✓ Disconnected successfully"); // Step 5: Verify we can't list buffers after disconnect @@ -632,10 +632,10 @@ async fn test_exec_lua_tool() -> Result<(), Box> { .await?; info!("Exec Lua result: {:#?}", result); - assert!(!result.content.is_empty()); + assert!(!result.content.as_ref().is_none_or(|c| c.is_empty())); // Verify the response contains Lua result - if let Some(content) = result.content.first() { + if let Some(content) = result.content.as_ref().and_then(|c| c.first()) { if let Some(text) = content.as_text() { assert!(text.text.contains("42")); } else { @@ -663,7 +663,7 @@ async fn test_exec_lua_tool() -> Result<(), Box> { }) .await?; - assert!(!result.content.is_empty()); + assert!(!result.content.as_ref().is_none_or(|c| c.is_empty())); // Test error handling for invalid Lua let mut invalid_lua_args = Map::new(); @@ -787,10 +787,10 @@ async fn test_lsp_clients_tool() -> Result<(), Box> { .await?; info!("LSP clients result: {:#?}", result); - assert!(!result.content.is_empty()); + assert!(!result.content.as_ref().is_none_or(|c| c.is_empty())); // Verify the response contains content - if let Some(_content) = result.content.first() { + if let Some(_content) = result.content.as_ref().and_then(|c| c.first()) { // Content received successfully - the JSON parsing is handled by the MCP framework info!("LSP clients content received successfully"); } else {