diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index 4272d04..fce32e9 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -53,6 +53,10 @@ jobs: if: steps.version.outputs.SKIP != 'true' run: nix build .#streamd-windows -o result-windows + - name: Build Zed extension + if: steps.version.outputs.SKIP != 'true' + run: nix build .#zed-extension-zip -o result-zed-extension-zip + - name: Prepare release artifacts if: steps.version.outputs.SKIP != 'true' run: | @@ -60,6 +64,7 @@ jobs: cp result-deb release/streamd_${{ steps.version.outputs.VERSION }}_amd64.deb cp result-musl/bin/streamd release/streamd-${{ steps.version.outputs.VERSION }}-linux-x86_64 cp result-windows/bin/streamd.exe release/streamd-${{ steps.version.outputs.VERSION }}-windows-x86_64.exe + cp result-zed-extension-zip release/streamd-zed-extension-${{ steps.version.outputs.VERSION }}.zip - name: Create release if: steps.version.outputs.SKIP != 'true' diff --git a/Cargo.lock b/Cargo.lock index d70fe33..90551b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -91,6 +91,28 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "auto_impl" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -123,9 +145,15 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.11.0" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "bumpalo" @@ -133,6 +161,12 @@ version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + [[package]] name = "cc" version = "1.2.60" @@ -234,6 +268,39 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "diff" version = "0.1.13" @@ -261,6 +328,17 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "either" version = "1.15.0" @@ -301,6 +379,91 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + [[package]] name = "getopts" version = "0.2.24" @@ -340,6 +503,12 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.5" @@ -361,6 +530,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + [[package]] name = "iana-time-zone" version = "0.1.65" @@ -385,12 +560,115 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + [[package]] name = "id-arena" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "indexmap" version = "2.14.0" @@ -467,12 +745,40 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "lsp-types" +version = "0.94.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66bfd44a06ae10647fe3f8214762e9369fd4248df1350924b4ef9e770a85ea1" +dependencies = [ + "bitflags 1.3.2", + "serde", + "serde_json", + "serde_repr", + "url", +] + [[package]] name = "memchr" version = "2.8.0" @@ -560,6 +866,25 @@ version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] name = "phf" version = "0.12.1" @@ -578,6 +903,41 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pin-project" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + [[package]] name = "pretty_assertions" version = "1.4.1" @@ -613,7 +973,7 @@ version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c3a14896dfa883796f1cb410461aef38810ea05f2b2c33c5aded3649095fdad" dependencies = [ - "bitflags", + "bitflags 2.11.1", "getopts", "memchr", "pulldown-cmark-escape", @@ -641,6 +1001,15 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.1", +] + [[package]] name = "redox_users" version = "0.5.2" @@ -693,7 +1062,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags", + "bitflags 2.11.1", "errno", "libc", "linux-raw-sys", @@ -715,6 +1084,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "semver" version = "1.0.28" @@ -764,6 +1139,17 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_spanned" version = "1.1.1" @@ -785,14 +1171,33 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + [[package]] name = "streamd" -version = "0.2.4" +version = "0.2.5" dependencies = [ "chrono", "chrono-tz", "clap", "clap_complete", + "dashmap 6.1.0", "directories", "indexmap", "itertools", @@ -802,9 +1207,12 @@ dependencies = [ "pulldown-cmark", "regex", "serde", + "serde_json", "tempfile", "thiserror", + "tokio", "toml", + "tower-lsp", "walkdir", ] @@ -846,6 +1254,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tempfile" version = "3.27.0" @@ -899,6 +1318,38 @@ dependencies = [ "syn", ] +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.52.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +dependencies = [ + "pin-project-lite", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "1.1.2+spec-1.1.0" @@ -938,6 +1389,97 @@ version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-lsp" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ba052b54a6627628d9b3c34c176e7eda8359b7da9acd497b9f20998d118508" +dependencies = [ + "async-trait", + "auto_impl", + "bytes", + "dashmap 5.5.3", + "futures", + "httparse", + "lsp-types", + "memchr", + "serde", + "serde_json", + "tokio", + "tokio-util", + "tower", + "tower-lsp-macros", + "tracing", +] + +[[package]] +name = "tower-lsp-macros" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + [[package]] name = "unicase" version = "2.9.0" @@ -974,6 +1516,25 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -998,11 +1559,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -1011,7 +1572,7 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] @@ -1087,7 +1648,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags", + "bitflags 2.11.1", "hashbrown 0.15.5", "indexmap", "semver", @@ -1185,6 +1746,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -1234,7 +1801,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags", + "bitflags 2.11.1", "indexmap", "log", "serde", @@ -1264,12 +1831,95 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + [[package]] name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zmij" version = "1.0.21" diff --git a/Cargo.toml b/Cargo.toml index dc2b964..2d70525 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "streamd" -version = "0.2.4" +version = "0.2.5" edition = "2021" description = "Personal knowledge management and time-tracking CLI using @Tag annotations" license = "AGPL-3.0-only" @@ -11,6 +11,7 @@ repository = "https://github.com/konstantinfickel/streamd" clap = { version = "4", features = ["derive", "env"] } clap_complete = "4" serde = { version = "1", features = ["derive"] } +serde_json = "1" toml = "1.0" thiserror = "2" miette = { version = "7", features = ["fancy"] } @@ -23,6 +24,9 @@ walkdir = "2" indexmap = { version = "2", features = ["serde"] } itertools = "0.14" directories = "6" +tower-lsp = "0.20" +tokio = { version = "1", features = ["rt-multi-thread", "io-std"] } +dashmap = "6" [dev-dependencies] pretty_assertions = "=1.4.1" diff --git a/README.md b/README.md index cf40451..52b17e6 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ Within files, `@`-prefixed markers at the beginning of paragraphs or headings de - `streamd todo --show-future` — Include tasks with future dates in the listing - `streamd edit [number]` — Edit a stream file by index (most recent first) - `streamd timesheet` — Generate time reports from `@Timesheet` markers +- `streamd lsp` — Start the LSP server (stdin/stdout transport; see [Editor Integration](#editor-integration) below) ## Configuration @@ -110,3 +111,122 @@ Running `streamd todo` finds all shards marked as open tasks and displays them n You can quickly edit or complete tasks by number: - `streamd todo 1 edit` opens task 1 in your editor at the correct line - `streamd todo 1 done` marks task 1 as done by inserting `@Done` after `@Task` + +## Editor Integration + +`streamd lsp` starts a Language Server Protocol server that provides IDE features for your stream markdown files. The server communicates over **stdin/stdout** and auto-activates only when a `.streamd.toml` file is present in the workspace root. + +### Features + +| Feature | Description | +|---|---| +| `@` completions | Suggests known markers from your config; conditional suggestions (e.g. `@Done` when `@Task` is on the line) | +| Temporal snippets | `@` followed by a digit offers `YYYYMMDD` / `HHMMSS` format snippets | +| Diagnostics | File-name format warnings (R15); timesheet errors (overlapping timecards, unclosed days) | +| Document symbols | Shard tree exposed as outline symbols | +| "Mark task as done" | Quick-fix code action: inserts `@Done` after `@Task` | +| Workspace symbols | Search shards across all `.md` files | +| References | Find all occurrences of an `@Marker` across the workspace | +| Rename | Rename an `@Marker` across all files | + +### Zed + +Add to `~/.config/zed/settings.json`: + +```json +{ + "languages": { + "Markdown": { + "language_servers": ["streamd-lsp", "..."] + } + }, + "lsp": { + "streamd-lsp": { + "binary": { + "path": "streamd", + "arguments": ["lsp"] + } + } + } +} +``` + +The `"..."` keeps Zed's default Markdown servers (e.g. `marksman`) active alongside streamd. + +#### Zed Extension (WSL2) + +If you run Zed on Windows with streamd installed inside WSL2, use the pre-built Zed extension instead of the manual config above. The extension auto-detects Windows and routes LSP communication through WSL2. + +**1. Install streamd in WSL2** (e.g. via the `.deb` package): + +```bash +wget https://git.konstantinfickel.de/kfickel/streamd/releases/download/vX.Y.Z/streamd_X.Y.Z_amd64.deb +sudo dpkg -i streamd_X.Y.Z_amd64.deb +``` + +**2. Download the extension** from the same release page: + +``` +streamd-zed-extension-X.Y.Z.zip +``` + +**3. Extract the zip** to a permanent folder on your Windows machine, e.g.: + +``` +C:\Users\\zed-extensions\streamd-zed-extension\ +``` + +The folder must contain `extension.toml` and `extension.wasm`. + +**4. Install the extension in Zed** via the command palette (`Ctrl+Shift+P`): + +``` +zed: install dev extension +``` + +Point Zed to the extracted folder. + +**5. Verify** by opening a Markdown file inside a directory that contains `.streamd.toml` — `@` completions and diagnostics should become active. + +### Neovim (nvim-lspconfig) + +**1. Register the server** — add to your Neovim config (e.g. `~/.config/nvim/init.lua` or a plugin file): + +```lua +local lspconfig = require('lspconfig') +local configs = require('lspconfig.configs') + +if not configs.streamd then + configs.streamd = { + default_config = { + cmd = { 'streamd', 'lsp' }, + filetypes = { 'markdown' }, + root_dir = lspconfig.util.root_pattern('.streamd.toml'), + single_file_support = false, + }, + } +end + +lspconfig.streamd.setup {} +``` + +The server activates automatically when Neovim opens a Markdown file inside a directory that contains a `.streamd.toml` file. + +**2. Using LSP features** — standard Neovim LSP keymaps apply (`:help lsp`): + +| Action | Default keymap | Notes | +|---|---|---| +| Trigger `@` completions | `` (insert mode) | Or via your completion plugin (`nvim-cmp`, `blink.cmp`, …) | +| Show diagnostics for current line | `d` / `gl` | File-name format warnings, timesheet errors | +| Jump to next / previous diagnostic | `]d` / `[d` | Navigate between warnings | +| Code actions (mark task as done) | `ca` (Neovim ≥ 0.10) | Place cursor on a line with `@Task` | +| Rename marker across all files | `cr` / `grn` | Renames the `@Marker` under the cursor everywhere | +| Find all references to a marker | `grr` / `fr` | Lists every occurrence of `@Marker` across the workspace | +| Document outline (shard tree) | `:lua vim.lsp.buf.document_symbol()` | Or via Telescope: `:Telescope lsp_document_symbols` | +| Workspace symbol search | `:lua vim.lsp.buf.workspace_symbol()` | Or via Telescope: `:Telescope lsp_workspace_symbols` | + +> **Note:** default keymaps (`grn`, `grr`, `d`, `]d`/`[d`) are available from Neovim 0.10+. On older versions use `:lua vim.lsp.buf.*` commands or set up keymaps manually in your `on_attach` callback. + +### VS Code (tasks.json / manual) + +Use any extension that lets you configure custom LSP servers, pointing `cmd` to `streamd lsp`. diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index 1c5b8ec..01b555f 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -400,6 +400,7 @@ Provide recursive search through the shard tree: | `streamd edit [n]` | Edit nth file (supports negative indexing for recent files) | | `streamd timesheet` | Generate formatted timesheet report with expected/actual hours | | `streamd completions ` | Generate shell completions (bash, zsh, fish, elvish, powershell) | +| `streamd lsp` | Start Language Server Protocol server over stdin/stdout | ### R21a: Daily Command Behavior @@ -471,3 +472,53 @@ Multiple configurations can be merged: - Dimensions are combined (later configs can add new dimensions) - Markers are combined (later configs can add new markers) - This allows base configuration + domain-specific extensions + +--- + +## LSP Server + +### R25: LSP Subcommand + +`streamd lsp` starts a Language Server Protocol server over stdin/stdout. + +**Workspace root resolution:** +- The base folder is taken from `initializeParams.rootUri` (or `rootPath` as fallback). +- R22/R23 global config resolution is bypassed in LSP mode. + +**Passive mode:** +- If `.streamd.toml` is absent from the workspace root, the server enters passive mode: all requests return empty results and no diagnostics are published. + +**Config watching:** +- The server registers a `workspace/didChangeWatchedFiles` watcher for `.streamd.toml`. +- Config is reloaded without restarting the server when `.streamd.toml` changes. + +**Document sync:** +- Full-document sync (`TextDocumentSyncKind::FULL`). +- Re-parses on `didOpen`, `didChange`, and `didSave`. + +### R25a: LSP Completion + +- Trigger character: `@` +- Returns marker names from the merged config (BasicTimesheetConfiguration + TaskConfiguration). +- Conditional suggestions: if marker A is on the line and A has placements with `if_with: {B}`, B is offered with higher priority. +- Temporal snippets: `@` followed by a digit offers `YYYYMMDD` and `HHMMSS` format snippets (R16). + +### R25b: LSP Diagnostics + +- **File-name format (R15)**: Warning when the file basename does not match `^(\d{8})(?:-(\d{4,6}))?.+\.md$`. +- **Timesheet violations (R18)**: Error when a day ends without a break; Warning for overlapping timecards. + +### R25c: LSP Document Symbols + +- Returns the `LocalizedShard` tree as nested `DocumentSymbol` nodes. +- Symbol names are derived from marker names or tag names. + +### R25d: LSP Code Actions + +- "Mark task as done": offered on any line containing `@Task` without `@Done`; inserts ` @Done` after `@Task`. + +### R25e: LSP Cross-file Features + +- `workspace/symbol`: searches all `.md` files in base folder (depth 1) for shards matching the query. +- `textDocument/references`: finds all occurrences of the `@Marker` under the cursor across the workspace. +- `textDocument/rename`: renames an `@Marker` across all files via `WorkspaceEdit`. diff --git a/flake.nix b/flake.nix index 12cdf69..46c7606 100644 --- a/flake.nix +++ b/flake.nix @@ -89,6 +89,41 @@ } ); + mkZedExtension = + system: + let + pkgs = mkPkgs system; + toolchain = pkgs.rust-bin.stable.latest.default.override { + targets = [ "wasm32-wasip1" ]; + }; + craneLib = (crane.mkLib pkgs).overrideToolchain toolchain; + extensionSrc = ./zed-extension; + vendoredDeps = craneLib.vendorCargoDeps { src = extensionSrc; }; + in + pkgs.stdenv.mkDerivation { + pname = "streamd-zed-extension"; + version = "0.0.1"; + src = extensionSrc; + + nativeBuildInputs = [ + toolchain + pkgs.cargo-component + ]; + + buildPhase = '' + export HOME=$TMPDIR + mkdir -p .cargo + cp ${vendoredDeps}/config.toml .cargo/config.toml + cargo component build --release --offline + ''; + + installPhase = '' + mkdir -p $out + cp extension.toml $out/ + cp target/wasm32-wasip1/release/streamd_zed.wasm $out/extension.wasm + ''; + }; + mkGitHooksCheck = system: let @@ -106,6 +141,36 @@ }; }; + mkGitHooksDev = + system: + let + pkgs = mkPkgs system; + toolchain = pkgs.rust-bin.stable.latest.default; + in + git-hooks.lib.${system}.run { + src = ./.; + hooks = { + rustfmt = { + enable = true; + package = toolchain; + }; + clippy = { + enable = true; + package = toolchain; + settings.denyWarnings = true; + }; + cargo-test = { + enable = true; + name = "cargo test"; + entry = "${toolchain}/bin/cargo test"; + pass_filenames = false; + language = "system"; + files = "\\.(rs|toml)$"; + }; + commitizen.enable = true; + }; + }; + mkMuslCraneLib = system: let @@ -164,6 +229,21 @@ in craneLib.buildPackage (commonArgs // { inherit cargoArtifacts; }); + mkZedExtensionZip = + system: + let + pkgs = mkPkgs system; + zed-extension = mkZedExtension system; + in + pkgs.runCommand "streamd-zed-extension-${version}.zip" { + nativeBuildInputs = [ pkgs.zip ]; + } '' + mkdir -p streamd-zed-extension + cp ${zed-extension}/extension.toml streamd-zed-extension/ + cp ${zed-extension}/extension.wasm streamd-zed-extension/ + zip -r $out streamd-zed-extension + ''; + mkStreamdDeb = system: let @@ -214,9 +294,11 @@ streamd-musl = mkStreamdMusl system; streamd-deb = mkStreamdDeb system; streamd-windows = mkStreamdWindows system; + zed-extension = mkZedExtension system; + zed-extension-zip = mkZedExtensionZip system; in { - inherit streamd streamd-musl streamd-deb streamd-windows; + inherit streamd streamd-musl streamd-deb streamd-windows zed-extension zed-extension-zip; default = streamd; } ); @@ -303,7 +385,7 @@ ]; shellHook = '' - ${(mkGitHooksCheck system).shellHook} + ${(mkGitHooksDev system).shellHook} ''; }; } diff --git a/src/cli/args.rs b/src/cli/args.rs index f45387f..5666337 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -72,4 +72,7 @@ pub enum Commands { #[arg(value_enum)] shell: Shell, }, + + /// Start LSP server (communicates over stdin/stdout) + Lsp, } diff --git a/src/cli/commands/daily.rs b/src/cli/commands/daily.rs index b202d0f..7edd317 100644 --- a/src/cli/commands/daily.rs +++ b/src/cli/commands/daily.rs @@ -65,7 +65,14 @@ pub fn run(date: Option) -> Result<(), StreamdError> { Command::new(&editor).arg(file_path).status()?; } else { let now_local = Utc::now().with_timezone(&tz); - let file_name = now_local.format("%Y%m%d-%H%M%S_daily.md").to_string(); + let file_timestamp = if target_date == now_local.date_naive() { + now_local + } else { + tz.from_local_datetime(&NaiveDateTime::new(target_date, NaiveTime::MIN)) + .earliest() + .unwrap() + }; + let file_name = file_timestamp.format("%Y%m%d-%H%M%S_daily.md").to_string(); let file_path = base_folder.join(&file_name); fs::write(&file_path, "# ")?; Command::new(&editor).arg(&file_path).status()?; diff --git a/src/cli/commands/lsp.rs b/src/cli/commands/lsp.rs new file mode 100644 index 0000000..88362d0 --- /dev/null +++ b/src/cli/commands/lsp.rs @@ -0,0 +1,1080 @@ +use std::fs; +use std::path::PathBuf; +use std::sync::Arc; + +use chrono::{Timelike, Utc}; +use chrono_tz::Tz; +use dashmap::DashMap; +use once_cell::sync::Lazy; +use regex::Regex; +use tokio::sync::RwLock; +use tower_lsp::jsonrpc::Result; +use tower_lsp::lsp_types::*; +use tower_lsp::{Client, LanguageServer, LspService, Server}; +use walkdir::WalkDir; + +use crate::error::StreamdError; +use crate::extract::parse_markdown_file; +use crate::localize::{localize_stream_file, merge_repository_configuration, TaskConfiguration}; +use crate::models::{LocalizedShard, RepositoryConfiguration}; +use crate::timesheet::{ + extract_timesheets, find_overlapping_timecards, load_repository_config, + BasicTimesheetConfiguration, +}; + +const MD_EXT: &str = "md"; + +/// R15 file name validation: YYYYMMDD[-HHMMSS][_type][...].md +/// The extraction regex requires at least one non-dot char after date/time. +static FILE_NAME_REGEX: Lazy = + Lazy::new(|| Regex::new(r"^(?P\d{8})(?:-(?P