Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b32ed48471 | ||
|
|
a4b77cd40b | ||
|
|
c137a6c586 | ||
|
|
7e6300f8e9 | ||
|
|
949e5f5df4 | ||
|
|
08df630a2b | ||
|
|
c8a62b9b42 | ||
|
|
34aeae0496 | ||
|
|
bc4f29393e | ||
|
|
dfa414163d | ||
|
|
a6b9910d39 | ||
|
|
cf11c5774d | ||
|
|
291e2e1832 | ||
|
|
bd772eec48 | ||
|
|
56dc5f4393 | ||
|
|
5dadaca767 |
@@ -1,5 +1,5 @@
|
||||
[language-server.silos]
|
||||
command = "./target/debug/silos"
|
||||
command = "silos"
|
||||
|
||||
[[language]]
|
||||
name = "go"
|
||||
|
||||
15
.vscode/settings.json
vendored
Normal file
15
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"vscode-lspconfig.serverConfigurations": [
|
||||
{
|
||||
"name": "silos",
|
||||
"document_selector": [
|
||||
{
|
||||
"language": "go"
|
||||
}
|
||||
],
|
||||
"command": [
|
||||
"silos"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
521
Cargo.lock
generated
521
Cargo.lock
generated
@@ -2,189 +2,6 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "actix-codec"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-http"
|
||||
version = "3.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44dfe5c9e0004c623edc65391dfd51daa201e7e30ebd9c9bedf873048ec32bc2"
|
||||
dependencies = [
|
||||
"actix-codec",
|
||||
"actix-rt",
|
||||
"actix-service",
|
||||
"actix-utils",
|
||||
"base64 0.22.1",
|
||||
"bitflags 2.9.1",
|
||||
"brotli",
|
||||
"bytes",
|
||||
"bytestring",
|
||||
"derive_more",
|
||||
"encoding_rs",
|
||||
"flate2",
|
||||
"foldhash",
|
||||
"futures-core",
|
||||
"h2 0.3.26",
|
||||
"http 0.2.12",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"language-tags",
|
||||
"local-channel",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rand 0.9.1",
|
||||
"sha1",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-macros"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-router"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8"
|
||||
dependencies = [
|
||||
"bytestring",
|
||||
"cfg-if",
|
||||
"http 0.2.12",
|
||||
"regex",
|
||||
"regex-lite",
|
||||
"serde",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-rt"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-server"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502"
|
||||
dependencies = [
|
||||
"actix-rt",
|
||||
"actix-service",
|
||||
"actix-utils",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"mio",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-service"
|
||||
version = "2.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-utils"
|
||||
version = "3.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8"
|
||||
dependencies = [
|
||||
"local-waker",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-web"
|
||||
version = "4.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a597b77b5c6d6a1e1097fddde329a83665e25c5437c696a3a9a4aa514a614dea"
|
||||
dependencies = [
|
||||
"actix-codec",
|
||||
"actix-http",
|
||||
"actix-macros",
|
||||
"actix-router",
|
||||
"actix-rt",
|
||||
"actix-server",
|
||||
"actix-service",
|
||||
"actix-utils",
|
||||
"actix-web-codegen",
|
||||
"bytes",
|
||||
"bytestring",
|
||||
"cfg-if",
|
||||
"cookie",
|
||||
"derive_more",
|
||||
"encoding_rs",
|
||||
"foldhash",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"impl-more",
|
||||
"itoa",
|
||||
"language-tags",
|
||||
"log",
|
||||
"mime",
|
||||
"once_cell",
|
||||
"pin-project-lite",
|
||||
"regex",
|
||||
"regex-lite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"smallvec",
|
||||
"socket2",
|
||||
"time",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-web-codegen"
|
||||
version = "4.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8"
|
||||
dependencies = [
|
||||
"actix-router",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.24.2"
|
||||
@@ -209,21 +26,6 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alloc-no-stdlib"
|
||||
version = "2.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
|
||||
|
||||
[[package]]
|
||||
name = "alloc-stdlib"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
|
||||
dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.19"
|
||||
@@ -386,36 +188,6 @@ version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "brotli"
|
||||
version = "8.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d"
|
||||
dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
"alloc-stdlib",
|
||||
"brotli-decompressor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "brotli-decompressor"
|
||||
version = "5.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03"
|
||||
dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
"alloc-stdlib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.18.1"
|
||||
@@ -454,15 +226,6 @@ version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||
|
||||
[[package]]
|
||||
name = "bytestring"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e465647ae23b2823b0753f50decb2d5a86d2bb2cac04788fafd1f80e45378e5f"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "candle-core"
|
||||
version = "0.9.1"
|
||||
@@ -525,8 +288,6 @@ version = "1.2.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "956a5e21988b87f372569b66183b78babf23ebc2e744b733e4350a752c4dafac"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
@@ -595,17 +356,6 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"time",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
@@ -622,15 +372,6 @@ version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
@@ -671,16 +412,6 @@ version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.11"
|
||||
@@ -729,15 +460,6 @@ dependencies = [
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.4.1"
|
||||
@@ -801,16 +523,6 @@ dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "5.0.1"
|
||||
@@ -959,12 +671,6 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
@@ -1315,16 +1021,6 @@ dependencies = [
|
||||
"seq-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.16"
|
||||
@@ -1360,25 +1056,6 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.10"
|
||||
@@ -1390,7 +1067,7 @@ dependencies = [
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http 1.3.1",
|
||||
"http",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
@@ -1444,7 +1121,7 @@ checksum = "cc03dcb0b0a83ae3f3363ec811014ae669f083e4e499c66602f447c4828737a1"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
"futures",
|
||||
"http 1.3.1",
|
||||
"http",
|
||||
"indicatif",
|
||||
"libc",
|
||||
"log",
|
||||
@@ -1475,17 +1152,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.3.1"
|
||||
@@ -1504,7 +1170,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http 1.3.1",
|
||||
"http",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1515,7 +1181,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http 1.3.1",
|
||||
"http",
|
||||
"http-body",
|
||||
"pin-project-lite",
|
||||
]
|
||||
@@ -1526,12 +1192,6 @@ version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.6.0"
|
||||
@@ -1541,8 +1201,8 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"h2 0.4.10",
|
||||
"http 1.3.1",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"itoa",
|
||||
@@ -1558,7 +1218,7 @@ version = "0.27.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
|
||||
dependencies = [
|
||||
"http 1.3.1",
|
||||
"http",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"rustls",
|
||||
@@ -1595,7 +1255,7 @@ dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http 1.3.1",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"ipnet",
|
||||
@@ -1723,12 +1383,6 @@ dependencies = [
|
||||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "impl-more"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.9.0"
|
||||
@@ -1798,16 +1452,6 @@ version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.77"
|
||||
@@ -1830,12 +1474,6 @@ dependencies = [
|
||||
"winnow 0.6.24",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "language-tags"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
@@ -1886,23 +1524,6 @@ version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
|
||||
|
||||
[[package]]
|
||||
name = "local-channel"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"local-waker",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "local-waker"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.13"
|
||||
@@ -2014,7 +1635,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
@@ -2111,12 +1731,6 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.46"
|
||||
@@ -2294,16 +1908,6 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.11"
|
||||
@@ -2382,12 +1986,6 @@ dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
@@ -2623,12 +2221,6 @@ dependencies = [
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-lite"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
@@ -2646,8 +2238,8 @@ dependencies = [
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2 0.4.10",
|
||||
"http 1.3.1",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
@@ -2888,17 +2480,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
@@ -2914,20 +2495,10 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "silos"
|
||||
version = "1.1.0"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"actix-web",
|
||||
"anyhow",
|
||||
"candle-core",
|
||||
"candle-nn",
|
||||
@@ -3165,37 +2736,6 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"num-conv",
|
||||
"powerfmt",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.1"
|
||||
@@ -3248,9 +2788,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.52.0",
|
||||
@@ -3355,7 +2893,7 @@ dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.3.1",
|
||||
"http",
|
||||
"http-body",
|
||||
"iri-string",
|
||||
"pin-project-lite",
|
||||
@@ -3416,7 +2954,6 @@ version = "0.1.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
@@ -3524,12 +3061,6 @@ version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||
|
||||
[[package]]
|
||||
name = "ug"
|
||||
version = "0.4.0"
|
||||
@@ -4273,31 +3804,3 @@ dependencies = [
|
||||
"num_enum",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
|
||||
dependencies = [
|
||||
"zstd-safe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-safe"
|
||||
version = "7.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
|
||||
dependencies = [
|
||||
"zstd-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "2.0.15+zstd.1.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
[package]
|
||||
name = "silos"
|
||||
version = "2.0.0"
|
||||
version = "4.0.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
actix-web = "4.11.0"
|
||||
anyhow = "1.0.98"
|
||||
candle-core = "0.9.1"
|
||||
candle-nn = "0.9.1"
|
||||
candle-transformers = "0.9.1"
|
||||
clap = { version = "4.5.39", features = ["derive"] }
|
||||
derive_more = "2.0.1"
|
||||
derive_more = { version = "2.0.1", features = ["display", "error"] }
|
||||
glob = "0.3.2"
|
||||
hf-hub = "0.4.2"
|
||||
hora = "0.1.1"
|
||||
|
||||
91
README.md
91
README.md
@@ -2,7 +2,9 @@
|
||||
|
||||
Dumb, proomptable modular snippet search.
|
||||
|
||||
## Getting started
|
||||
## Installation
|
||||
|
||||
### Binary releases
|
||||
|
||||
There are no binary releases yet.
|
||||
|
||||
@@ -13,26 +15,48 @@ Prerequisites:
|
||||
- libc
|
||||
- [rust toolchain](https://rustup.rs)
|
||||
|
||||
Clone this repository and enter it
|
||||
Clone this repository and build it.
|
||||
|
||||
``` sh
|
||||
git clone https://github.com/lavafroth/silos
|
||||
cd silos
|
||||
cargo install --git https://github.com/lavafroth/silos
|
||||
```
|
||||
|
||||
``` sh
|
||||
cargo r http
|
||||
## Editor support
|
||||
|
||||
- Helix: Use the example `.helix` directory provided to run the LSP for files under `./examples/`.
|
||||
- Neovim: Please follow [the official guide](https://neovim.io/doc/user/lsp.html).
|
||||
- VSCode: Use the [vscode-lspconfig](https://marketplace.visualstudio.com/items?itemName=whtsht.vscode-lspconfig) extension with the `.vscode/settings.json` provided.
|
||||
|
||||
Make sure to modify the binary path in the example to where you have it on your system.
|
||||
|
||||
## Usage
|
||||
|
||||
- Write a comment above a paragraph of code, consider the example in examples/example.go
|
||||
|
||||
``` go
|
||||
resumeFilename := "resume.pdf"
|
||||
version := 3
|
||||
// refactor: change the file basename to that of the parent
|
||||
whereIsMyResume :=
|
||||
filepath.Base(
|
||||
documentsDirectory + "CV" + "_v" + strconv.Itoa(version) + "/" + resumeFilename)
|
||||
```
|
||||
|
||||
- The comment must begin with either of
|
||||
- `generate: `
|
||||
- `refactor: `
|
||||
- Select the code to be modified along with the comment above it.
|
||||
- Trigger code actions. In helix, this is `space`, `a`.
|
||||
- Select the option called "ask silos."
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Embedding defaults to using the CPU. You may use the `--gpu` flag with a GPU number to use a dedicated GPU.
|
||||
|
||||
An HTTP REST API listens on port 8000 and can be queried for code snippets.
|
||||
## `generate` snippets
|
||||
|
||||
### v1 API
|
||||
|
||||
V1 snippets are stored in the KDL format inside per-language directories under `./snippets/v1`. They must conform to the following structure
|
||||
- Stored in the KDL format inside per-language directories under `./snippets/v1`.
|
||||
- They must conform to the following structure
|
||||
|
||||
``` kdl
|
||||
desc "describes the snippet"
|
||||
@@ -43,35 +67,17 @@ KDL supports arbitrary raw strings with as many `#`s before and after the quotes
|
||||
|
||||
See the example snippet `./snippets/v1/go/simple_worker.kdl` in the go programming language.
|
||||
|
||||
#### Querying
|
||||
## `refactor` snippets
|
||||
|
||||
We recommend the `jo` CLI to easily generate JSON payloads for the API.
|
||||
This API parses code into an AST (Abstract Syntax Tree) via tree-sitter and can perform subsequent mutations.
|
||||
|
||||
``` sh
|
||||
jo desc="channeled worker in go" \
|
||||
curl http://localhost:8000/api/v1/get --json @-
|
||||
```
|
||||
|
||||
You must add the "in someLanguage" suffix to your query's description field. This was a bad design choice and will be deprecated in a later release.
|
||||
|
||||
#### Adding a snippet
|
||||
|
||||
``` sh
|
||||
curl http://localhost:8000/api/v1/add --json \
|
||||
'{ "desc": "Build an asynchronous shared mutable state", "lang": "rust", "body": "let object = Arc::new(Mutex::new(old));" }'
|
||||
```
|
||||
|
||||
### v2 API
|
||||
|
||||
The v2 API leverages tree-sitter to parse code into an AST (Abstract Syntax Tree) and perform subsequent mutations on the code.
|
||||
|
||||
#### Supported Languages
|
||||
### Supported Languages
|
||||
|
||||
- C
|
||||
- Rust
|
||||
- Go
|
||||
|
||||
#### Defining mutation collections
|
||||
### Defining mutation collections
|
||||
|
||||
``` kdl
|
||||
description "describes the mutation collection"
|
||||
@@ -101,26 +107,9 @@ mutation {
|
||||
|
||||
See the example mutation collection in `./snippets/v2/go/mutations.kdl`.
|
||||
|
||||
#### Querying
|
||||
|
||||
``` sh
|
||||
jo body=@examples/example.go \
|
||||
desc='change the current filepath to the parent filepath in go' \
|
||||
| curl http://localhost:8000/api/v2/get --json @-
|
||||
```
|
||||
|
||||
V2 queries have the following fields
|
||||
|
||||
- `desc`: Description of the query.
|
||||
- `body`: The code to be parsed and modified.
|
||||
|
||||
The API performs a single-pass substitution based on the closest matching mutation. Captured groups are used within the `substitute` block and the mutated code is returned in the response JSON `body` field.
|
||||
- The API performs a single-pass substitution based on the closest matching mutation.
|
||||
- Captured groups are used within the `substitute` block and the mutated code is returned.
|
||||
|
||||
**Further reading**
|
||||
|
||||
- [tree-sitter query snytax](https://tree-sitter.github.io/tree-sitter/using-parsers/queries/1-syntax.html) to create mutation expressions.
|
||||
- [jo](https://github.com/jpmens/jo) to build the JSON body from a file.
|
||||
|
||||
## Coming soon
|
||||
|
||||
An LSP to provide Silos code actions for a given selection.
|
||||
|
||||
25
src/args.rs
25
src/args.rs
@@ -3,9 +3,6 @@ use clap::Parser;
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
pub(crate) struct Args {
|
||||
/// The mode to run the server in. Defaults to LSP. The HTTP REST API can be run by specifying `http` or `http:port`. For example: `http:7047`
|
||||
pub(crate) mode: Option<String>,
|
||||
|
||||
/// Run on the Nth GPU device.
|
||||
#[arg(long)]
|
||||
pub(crate) gpu: Option<usize>,
|
||||
@@ -19,11 +16,6 @@ pub(crate) struct Args {
|
||||
pub(crate) revision: Option<String>,
|
||||
}
|
||||
|
||||
pub enum RunMode {
|
||||
Http(u16),
|
||||
Lsp,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
pub(crate) fn resolve_model_and_revision(&self) -> (String, String) {
|
||||
let default_model = "sentence-transformers/all-MiniLM-L6-v2".to_string();
|
||||
@@ -36,21 +28,4 @@ impl Args {
|
||||
(None, None) => (default_model, default_revision),
|
||||
}
|
||||
}
|
||||
pub(crate) fn mode(&self) -> RunMode {
|
||||
let Some(http) = &self.mode else {
|
||||
return RunMode::Lsp;
|
||||
};
|
||||
if http == "http" {
|
||||
return RunMode::Http(8000);
|
||||
}
|
||||
let Some(port) = http.strip_prefix("http:") else {
|
||||
return RunMode::Lsp;
|
||||
};
|
||||
|
||||
let Ok(port) = port.parse() else {
|
||||
return RunMode::Lsp;
|
||||
};
|
||||
|
||||
RunMode::Http(port)
|
||||
}
|
||||
}
|
||||
|
||||
26
src/embed.rs
26
src/embed.rs
@@ -8,9 +8,16 @@ use hf_hub::RepoType;
|
||||
use hf_hub::api::sync::Api;
|
||||
use std::path::PathBuf;
|
||||
use tokenizers::Tokenizer;
|
||||
use tokenizers::TokenizerImpl;
|
||||
use tokenizers::ModelWrapper;
|
||||
use tokenizers::NormalizerWrapper;
|
||||
use tokenizers::PreTokenizerWrapper;
|
||||
use tokenizers::PostProcessorWrapper;
|
||||
use tokenizers::DecoderWrapper;
|
||||
|
||||
pub struct Embed {
|
||||
model: BertModel,
|
||||
tokenizer: Tokenizer,
|
||||
tokenizer: TokenizerImpl<ModelWrapper, NormalizerWrapper, PreTokenizerWrapper, PostProcessorWrapper, DecoderWrapper>,
|
||||
}
|
||||
|
||||
impl Embed {
|
||||
@@ -26,11 +33,16 @@ impl Embed {
|
||||
|
||||
let config = std::fs::read_to_string(config_path)?;
|
||||
let config: Config = serde_json::from_str(&config)?;
|
||||
let tokenizer = Tokenizer::from_file(tokenizer_path).map_err(E::msg)?;
|
||||
let mut tokenizer = Tokenizer::from_file(tokenizer_path).map_err(E::msg)?;
|
||||
|
||||
let vb = unsafe { VarBuilder::from_mmaped_safetensors(&[weights_path], DTYPE, &device)? };
|
||||
let model = BertModel::load(vb, &config)?;
|
||||
|
||||
let tokenizer = tokenizer
|
||||
.with_padding(None)
|
||||
.with_truncation(None)
|
||||
.map_err(E::msg)?.clone();
|
||||
|
||||
Ok(Embed { model, tokenizer })
|
||||
}
|
||||
|
||||
@@ -45,14 +57,8 @@ impl Embed {
|
||||
Ok((config, tokenizer, weights))
|
||||
}
|
||||
|
||||
pub(crate) fn embed(&mut self, prompt: &str) -> Result<Vec<f32>> {
|
||||
let tokenizer = self
|
||||
.tokenizer
|
||||
.with_padding(None)
|
||||
.with_truncation(None)
|
||||
.map_err(E::msg)?;
|
||||
|
||||
let tokens = tokenizer
|
||||
pub(crate) fn embed(&self, prompt: &str) -> Result<Vec<f32>> {
|
||||
let tokens = self.tokenizer
|
||||
.encode(prompt, true)
|
||||
.map_err(E::msg)?
|
||||
.get_ids()
|
||||
|
||||
112
src/lsp.rs
112
src/lsp.rs
@@ -1,20 +1,16 @@
|
||||
use crate::StateWrapper;
|
||||
use crate::v2;
|
||||
use actix_web::web::Data;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use tower_lsp::lsp_types::*;
|
||||
use tower_lsp::{Client, LanguageServer};
|
||||
use tracing::error;
|
||||
|
||||
pub struct Backend {
|
||||
pub client: Client,
|
||||
pub body: Arc<Mutex<String>>,
|
||||
pub appstate: Data<StateWrapper>,
|
||||
pub appstate: crate::State,
|
||||
}
|
||||
|
||||
pub fn string_range_index(s: &str, r: Range) -> &str {
|
||||
fn string_range_index(s: &str, r: Range) -> &str {
|
||||
let mut newline_count = 0;
|
||||
let mut start = None;
|
||||
let mut end = None;
|
||||
@@ -44,11 +40,9 @@ impl LanguageServer for Backend {
|
||||
text_document_sync: Some(TextDocumentSyncCapability::Kind(
|
||||
TextDocumentSyncKind::FULL,
|
||||
)),
|
||||
code_action_provider: Some(
|
||||
tower_lsp::lsp_types::CodeActionProviderCapability::Options(
|
||||
CodeActionOptions::default(),
|
||||
),
|
||||
),
|
||||
code_action_provider: Some(CodeActionProviderCapability::Options(
|
||||
CodeActionOptions::default(),
|
||||
)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
@@ -81,48 +75,52 @@ impl LanguageServer for Backend {
|
||||
params: CodeActionParams,
|
||||
) -> tower_lsp::jsonrpc::Result<Option<CodeActionResponse>> {
|
||||
let uri = params.text_document.uri;
|
||||
let extension = url_extension(&uri);
|
||||
let Some(lang) = url_extension(&uri) else {
|
||||
self.client
|
||||
.log_message(MessageType::ERROR, "unable to determine filetype, file has no extension")
|
||||
.await;
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let body = self.body.lock().await.to_string();
|
||||
let mut range = params.range;
|
||||
let selected_text = string_range_index(&body, range);
|
||||
|
||||
let range = params.range;
|
||||
let new_text = string_range_index(&body, range);
|
||||
let Some((_before, after)) = new_text.split_once("silos: ") else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Some((desc, _after)) = after.split_once("\n") else {
|
||||
let Some(comment) = ParsedAction::new(selected_text) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let (prompt, lang) = if let Some(ext) = extension {
|
||||
(desc, ext)
|
||||
} else if let Some((prompt, lang)) = desc.rsplit_once(" in ") {
|
||||
(prompt, lang.to_string())
|
||||
} else {
|
||||
error!("{}", v2::errors::Error::MissingSuffix);
|
||||
return Ok(None);
|
||||
let action_response = match comment.action {
|
||||
Action::Generate => {
|
||||
range.start = range.end;
|
||||
self.appstate.generate(&lang, comment.description, 1)
|
||||
.map(|v| v.into_iter().map(|s| format!("{s}\n")).collect())
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
Action::Refactor => {
|
||||
self.appstate.refactor(&lang, comment.description, selected_text, 1)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
};
|
||||
|
||||
let closest_matches =
|
||||
match v2::api::closest_mutation(&lang, prompt, &body, 1, &self.appstate) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
let closest_matches = match action_response {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
self.client
|
||||
.log_message(MessageType::ERROR, e.to_string())
|
||||
.await;
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
|
||||
let Some(closest) = closest_matches.into_iter().next() else {
|
||||
let Some(new_text) = closest_matches.into_iter().next() else {
|
||||
return Ok(None);
|
||||
};
|
||||
let text_edit = TextEdit {
|
||||
range,
|
||||
new_text: closest,
|
||||
};
|
||||
let text_edit = TextEdit { range, new_text };
|
||||
let changes: HashMap<Url, _> = [(uri, vec![text_edit])].into_iter().collect();
|
||||
let edit = Some(WorkspaceEdit {
|
||||
changes: Some(changes),
|
||||
document_changes: None,
|
||||
change_annotations: None,
|
||||
..Default::default()
|
||||
});
|
||||
let actions = vec![CodeActionOrCommand::CodeAction(CodeAction {
|
||||
title: "ask silos".to_string(),
|
||||
@@ -133,6 +131,40 @@ impl LanguageServer for Backend {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ParsedAction<'a> {
|
||||
action: Action,
|
||||
description: &'a str,
|
||||
}
|
||||
|
||||
pub enum Action {
|
||||
Generate,
|
||||
Refactor,
|
||||
}
|
||||
|
||||
impl<'a> ParsedAction<'a> {
|
||||
fn new(comment: &'a str) -> Option<ParsedAction<'a>> {
|
||||
let upto_newline = match comment.rsplit_once("\n") {
|
||||
Some((upto_newline, _discard)) => upto_newline,
|
||||
None => comment,
|
||||
};
|
||||
let maybe_generate =
|
||||
upto_newline
|
||||
.split_once("generate: ")
|
||||
.map(|(_discard, description)| ParsedAction {
|
||||
action: Action::Generate,
|
||||
description,
|
||||
});
|
||||
let maybe_refactor =
|
||||
upto_newline
|
||||
.split_once("refactor: ")
|
||||
.map(|(_discard, description)| ParsedAction {
|
||||
action: Action::Refactor,
|
||||
description,
|
||||
});
|
||||
maybe_generate.or(maybe_refactor)
|
||||
}
|
||||
}
|
||||
|
||||
fn url_extension(u: &Url) -> Option<String> {
|
||||
let file_path = u.to_file_path().ok()?;
|
||||
|
||||
|
||||
56
src/main.rs
56
src/main.rs
@@ -1,10 +1,9 @@
|
||||
use actix_web::{App, HttpServer, web};
|
||||
use anyhow::{Context, Error as E, Result, bail};
|
||||
use clap::Parser;
|
||||
use hora::core::{ann_index::ANNIndex, metrics::Metric::Euclidean};
|
||||
use hora::index::hnsw_idx::HNSWIndex;
|
||||
use kdl::KdlDocument;
|
||||
use state::{State, StateWrapper};
|
||||
use state::State;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
@@ -14,8 +13,7 @@ mod args;
|
||||
mod embed;
|
||||
mod lsp;
|
||||
mod state;
|
||||
mod v1;
|
||||
mod v2;
|
||||
mod mutation;
|
||||
|
||||
fn path_to_parent_base(p: &std::path::Path) -> Result<String> {
|
||||
let Some(parent) = p
|
||||
@@ -29,26 +27,23 @@ fn path_to_parent_base(p: &std::path::Path) -> Result<String> {
|
||||
Ok(parent)
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
tracing_subscriber::fmt::init();
|
||||
let args = args::Args::parse();
|
||||
let mode = args.mode();
|
||||
let (model_id, revision) = args.resolve_model_and_revision();
|
||||
let mut embed = embed::Embed::new(args.gpu, &model_id, &revision)?;
|
||||
let embed = embed::Embed::new(args.gpu, &model_id, &revision)?;
|
||||
let mut dict = HashMap::default();
|
||||
let dimensions = 384;
|
||||
|
||||
let paths = glob::glob("./snippets/v1/*/*.kdl")?;
|
||||
for path in paths {
|
||||
let path = path?;
|
||||
let parent = path_to_parent_base(&path)?;
|
||||
|
||||
let current_lang_index = dict.entry(parent).or_insert_with(|| {
|
||||
let dimension = 384;
|
||||
let params = hora::index::hnsw_params::HNSWParams::<f32>::default();
|
||||
|
||||
HNSWIndex::<f32, String>::new(dimension, ¶ms)
|
||||
});
|
||||
let current_lang_index = dict
|
||||
.entry(parent)
|
||||
.or_insert_with(|| HNSWIndex::new(dimensions, &Default::default()));
|
||||
|
||||
let doc_str = std::fs::read_to_string(&path)?;
|
||||
let doc: KdlDocument = doc_str
|
||||
@@ -72,7 +67,7 @@ async fn main() -> Result<()> {
|
||||
.map_err(E::msg)?;
|
||||
}
|
||||
|
||||
// v2 stuff
|
||||
// v2
|
||||
let paths = glob::glob("./snippets/v2/*/*.kdl")?;
|
||||
let mut v2_dict = HashMap::new();
|
||||
let mut v2_mutations_collection = vec![];
|
||||
@@ -80,13 +75,10 @@ async fn main() -> Result<()> {
|
||||
let path = path?;
|
||||
let parent = path_to_parent_base(&path)?;
|
||||
|
||||
let mutations = v2::mutation::from_path(path)?;
|
||||
let current_lang_index = v2_dict.entry(parent).or_insert_with(|| {
|
||||
let dimension = 384;
|
||||
let params = hora::index::hnsw_params::HNSWParams::<f32>::default();
|
||||
|
||||
HNSWIndex::<f32, usize>::new(dimension, ¶ms)
|
||||
});
|
||||
let mutations = mutation::from_path(path)?;
|
||||
let current_lang_index = v2_dict
|
||||
.entry(parent)
|
||||
.or_insert_with(|| HNSWIndex::new(dimensions, &Default::default()));
|
||||
|
||||
current_lang_index
|
||||
.add(&embed.embed(&mutations.description)?, i)
|
||||
@@ -100,36 +92,20 @@ async fn main() -> Result<()> {
|
||||
|
||||
let appstate = State {
|
||||
embed,
|
||||
v1: v1::api::State { dict },
|
||||
v2: v2::api::State {
|
||||
generate: state::Generate { dict },
|
||||
refactor: state::Refactor {
|
||||
dict: v2_dict,
|
||||
mutations_collection: v2_mutations_collection,
|
||||
},
|
||||
};
|
||||
|
||||
let appstate_wrapped = web::Data::new(appstate.build());
|
||||
|
||||
if let args::RunMode::Http(port) = mode {
|
||||
return HttpServer::new(move || {
|
||||
App::new()
|
||||
.app_data(appstate_wrapped.clone())
|
||||
.service(v1::api::get_snippet)
|
||||
.service(v1::api::add_snippet)
|
||||
.service(v2::api::get_snippet)
|
||||
})
|
||||
.bind(("127.0.0.1", port))?
|
||||
.run()
|
||||
.await
|
||||
.map_err(E::from);
|
||||
};
|
||||
|
||||
let stdin = tokio::io::stdin();
|
||||
let stdout = tokio::io::stdout();
|
||||
|
||||
let (service, socket) = LspService::new(|client| lsp::Backend {
|
||||
client,
|
||||
body: Arc::new(Mutex::new(String::default())),
|
||||
appstate: appstate_wrapped.clone(),
|
||||
appstate
|
||||
});
|
||||
Server::new(stdin, stdout, socket).serve(service).await;
|
||||
Ok(())
|
||||
|
||||
117
src/state.rs
117
src/state.rs
@@ -1,19 +1,118 @@
|
||||
use std::sync::Mutex;
|
||||
use derive_more::Display;
|
||||
use derive_more::Error;
|
||||
use tree_sitter::Parser;
|
||||
use std::collections::HashMap;
|
||||
use hora::index::hnsw_idx::HNSWIndex;
|
||||
use hora::core::ann_index::ANNIndex;
|
||||
use crate::mutation;
|
||||
|
||||
pub struct StateWrapper {
|
||||
pub inner: Mutex<State>,
|
||||
#[derive(Debug, Display, Error)]
|
||||
pub enum Error {
|
||||
#[display("failed to embed prompt")]
|
||||
EmbedFailed,
|
||||
#[display("snippets were requested for an unknown language")]
|
||||
UnknownLang,
|
||||
#[display("failed to parse corpus of code to apply mutation on")]
|
||||
SnippetParsing,
|
||||
}
|
||||
|
||||
pub struct Refactor {
|
||||
pub dict: HashMap<String, HNSWIndex<f32, usize>>,
|
||||
pub mutations_collection: Vec<mutation::MutationCollection>,
|
||||
}
|
||||
|
||||
impl Refactor {
|
||||
fn get_lang(s: &str) -> Result<tree_sitter::Language, Error> {
|
||||
Ok(match s {
|
||||
"go" => tree_sitter_go::LANGUAGE,
|
||||
"c" => tree_sitter_c::LANGUAGE,
|
||||
"rs" => tree_sitter_rust::LANGUAGE,
|
||||
_ => return Err(Error::UnknownLang),
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
||||
pub fn search(
|
||||
&self,
|
||||
lang: &str,
|
||||
target: &[f32],
|
||||
body: &str,
|
||||
top_k: usize,
|
||||
) -> Result<Vec<String>, Error> {
|
||||
let langfn = Self::get_lang(lang)?;
|
||||
let mut parser = Parser::new();
|
||||
parser
|
||||
.set_language(&langfn)
|
||||
.map_err(|_| Error::UnknownLang)?;
|
||||
|
||||
let source_code = body;
|
||||
let source_bytes = source_code.as_bytes();
|
||||
let tree = parser
|
||||
.parse(source_code, None)
|
||||
.ok_or(Error::SnippetParsing)?;
|
||||
let root_node = tree.root_node();
|
||||
|
||||
// search for k nearest neighbors
|
||||
let collected = self.dict[lang]
|
||||
.search(target, top_k)
|
||||
.iter()
|
||||
.filter_map(|&index| {
|
||||
let applied = mutation::apply(
|
||||
langfn.clone(),
|
||||
source_bytes,
|
||||
root_node,
|
||||
&self.mutations_collection[index],
|
||||
);
|
||||
match applied {
|
||||
Ok(v) => Some(v),
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
collection_index = index,
|
||||
"failed to apply mutations from collection {}", e
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
Ok(collected)
|
||||
}
|
||||
}
|
||||
pub struct Generate {
|
||||
pub dict: HashMap<String, HNSWIndex<f32, String>>
|
||||
}
|
||||
|
||||
impl Generate {
|
||||
fn search(&self, lang: &str, target: &[f32], top_k: usize) -> Result<Vec<String>, Error> {
|
||||
let Some(snippets_for_lang) = self.dict.get(lang) else {
|
||||
return Err(Error::UnknownLang);
|
||||
};
|
||||
Ok(snippets_for_lang.search(target, top_k))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
// TODO: create new constructor and private these fields
|
||||
pub embed: crate::embed::Embed,
|
||||
pub v1: crate::v1::api::State,
|
||||
pub v2: crate::v2::api::State,
|
||||
pub generate: Generate,
|
||||
pub refactor: Refactor,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn build(self) -> StateWrapper {
|
||||
StateWrapper {
|
||||
inner: Mutex::new(self),
|
||||
}
|
||||
|
||||
pub fn generate(&self, lang: &str, prompt: &str, top_k: usize) -> Result<Vec<String>, Error> {
|
||||
let Ok(target) = self.embed.embed(prompt) else {
|
||||
return Err(Error::EmbedFailed);
|
||||
};
|
||||
|
||||
self.generate.search(lang, &target, top_k)
|
||||
}
|
||||
|
||||
pub fn refactor(&self, lang: &str, prompt: &str, body: &str, top_k: usize) -> Result<Vec<String>, Error> {
|
||||
let Ok(target) = self.embed.embed(prompt) else {
|
||||
return Err(Error::EmbedFailed);
|
||||
};
|
||||
|
||||
self.refactor.search(lang, &target, body, top_k)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
use hora::core::ann_index::ANNIndex;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::errors::Error;
|
||||
use actix_web::{Responder, post, web};
|
||||
use anyhow::Result;
|
||||
use hora::index::hnsw_idx::HNSWIndex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SnippetRequest {
|
||||
desc: String,
|
||||
top_k: Option<usize>,
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
pub dict: HashMap<String, HNSWIndex<f32, String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct SnippetResponse {
|
||||
id: usize,
|
||||
snippet: Snippet,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Snippet {
|
||||
lang: String,
|
||||
desc: String,
|
||||
body: String,
|
||||
}
|
||||
|
||||
#[post("/api/v1/get")]
|
||||
pub(crate) async fn get_snippet(
|
||||
data: web::Data<crate::state::StateWrapper>,
|
||||
snippet_request: web::Json<SnippetRequest>,
|
||||
) -> Result<impl Responder, Error> {
|
||||
let Some((prompt, lang)) = snippet_request.desc.rsplit_once(" in ") else {
|
||||
return Err(Error::MissingSuffix);
|
||||
};
|
||||
|
||||
let Ok(mut appstate) = data.inner.lock() else {
|
||||
return Err(Error::Busy);
|
||||
};
|
||||
|
||||
let Ok(target) = appstate.embed.embed(prompt) else {
|
||||
return Err(Error::EmbedFailed);
|
||||
};
|
||||
|
||||
let Some(snippets_for_lang) = appstate.v1.dict.get(lang) else {
|
||||
return Err(Error::UnknownLang);
|
||||
};
|
||||
// search for k nearest neighbors
|
||||
let closest = snippets_for_lang.search(&target, snippet_request.top_k.unwrap_or(1));
|
||||
Ok(web::Json(closest))
|
||||
}
|
||||
|
||||
#[post("/api/v1/add")]
|
||||
pub(crate) async fn add_snippet(
|
||||
data: web::Data<crate::state::StateWrapper>,
|
||||
snippet: web::Json<Snippet>,
|
||||
) -> Result<impl Responder, Error> {
|
||||
let Ok(mut appstate) = data.inner.lock() else {
|
||||
return Err(Error::Busy);
|
||||
};
|
||||
let Ok(embedding) = appstate.embed.embed(&snippet.desc) else {
|
||||
return Err(Error::EmbedFailed);
|
||||
};
|
||||
let index = appstate
|
||||
.v1
|
||||
.dict
|
||||
.entry(snippet.lang.clone())
|
||||
.or_insert_with(|| {
|
||||
let dimension = 384;
|
||||
let params = hora::index::hnsw_params::HNSWParams::<f32>::default();
|
||||
|
||||
HNSWIndex::<f32, String>::new(dimension, ¶ms)
|
||||
});
|
||||
index.add(&embedding, snippet.body.clone()).unwrap();
|
||||
index.build(hora::core::metrics::Metric::Euclidean).unwrap();
|
||||
|
||||
Ok(format!(
|
||||
"{} {} {}",
|
||||
snippet.body, snippet.lang, snippet.desc
|
||||
))
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
use actix_web::{
|
||||
HttpResponse, error,
|
||||
http::{StatusCode, header::ContentType},
|
||||
};
|
||||
use derive_more::derive::{Display, Error};
|
||||
use serde_json::json;
|
||||
|
||||
#[derive(Debug, Display, Error)]
|
||||
pub enum Error {
|
||||
#[display("the server is busy. come back later.")]
|
||||
Busy,
|
||||
#[display("end your request with ` in somelang`.")]
|
||||
MissingSuffix,
|
||||
#[display("failed to embed your prompt.")]
|
||||
EmbedFailed,
|
||||
#[display("snippets were requested for an unknown language")]
|
||||
UnknownLang,
|
||||
}
|
||||
|
||||
impl error::ResponseError for Error {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
let message = json!({
|
||||
"message": self.to_string(),
|
||||
})
|
||||
.to_string();
|
||||
HttpResponse::build(self.status_code())
|
||||
.insert_header(ContentType::json())
|
||||
.body(message)
|
||||
}
|
||||
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {
|
||||
Self::EmbedFailed => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Self::MissingSuffix | Self::UnknownLang => StatusCode::BAD_REQUEST,
|
||||
Self::Busy => StatusCode::GATEWAY_TIMEOUT,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub(crate) mod api;
|
||||
pub(crate) mod errors;
|
||||
118
src/v2/api.rs
118
src/v2/api.rs
@@ -1,118 +0,0 @@
|
||||
use hora::{core::ann_index::ANNIndex, index::hnsw_idx::HNSWIndex};
|
||||
use std::collections::HashMap;
|
||||
use tracing::{error, info};
|
||||
use tree_sitter::Parser;
|
||||
|
||||
use super::{errors::Error, mutation};
|
||||
use actix_web::{Responder, post, web};
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub struct State {
|
||||
pub dict: HashMap<String, HNSWIndex<f32, usize>>,
|
||||
pub mutations_collection: Vec<mutation::MutationCollection>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SnippetRequest {
|
||||
desc: String,
|
||||
body: String,
|
||||
top_k: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct SnippetResponse {
|
||||
id: usize,
|
||||
snippet: Snippet,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Snippet {
|
||||
lang: String,
|
||||
desc: String,
|
||||
body: String,
|
||||
}
|
||||
|
||||
pub fn get_lang(s: &str) -> Result<tree_sitter::Language, Error> {
|
||||
Ok(match s {
|
||||
"go" => tree_sitter_go::LANGUAGE,
|
||||
"c" => tree_sitter_c::LANGUAGE,
|
||||
"rust" => tree_sitter_rust::LANGUAGE,
|
||||
_ => return Err(Error::UnknownLang),
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
||||
#[post("/api/v2/get")]
|
||||
pub(crate) async fn get_snippet(
|
||||
data: web::Data<crate::state::StateWrapper>,
|
||||
snippet_request: web::Json<SnippetRequest>,
|
||||
) -> Result<impl Responder, Error> {
|
||||
let Some((prompt, lang)) = snippet_request.desc.rsplit_once(" in ") else {
|
||||
return Err(Error::MissingSuffix);
|
||||
};
|
||||
|
||||
let closest = closest_mutation(
|
||||
lang,
|
||||
prompt,
|
||||
snippet_request.body.as_str(),
|
||||
snippet_request.top_k.unwrap_or(1),
|
||||
&data,
|
||||
)?;
|
||||
Ok(web::Json(closest))
|
||||
}
|
||||
|
||||
pub fn closest_mutation(
|
||||
lang: &str,
|
||||
prompt: &str,
|
||||
body: &str,
|
||||
top_k: usize,
|
||||
data: &web::Data<crate::state::StateWrapper>,
|
||||
) -> Result<Vec<String>, Error> {
|
||||
let langfn = get_lang(lang)?;
|
||||
|
||||
info!(prompt = prompt, language = lang, "v2 request");
|
||||
|
||||
let mut appstate = data.inner.lock().map_err(|_| Error::Busy)?;
|
||||
let target = appstate
|
||||
.embed
|
||||
.embed(prompt)
|
||||
.map_err(|_| Error::EmbedFailed)?;
|
||||
let mut parser = Parser::new();
|
||||
parser
|
||||
.set_language(&langfn)
|
||||
.map_err(|_| Error::UnknownLang)?;
|
||||
|
||||
let source_code = body;
|
||||
let source_bytes = source_code.as_bytes();
|
||||
let tree = parser
|
||||
.parse(source_code, None)
|
||||
.ok_or(Error::SnippetParsing)?;
|
||||
let root_node = tree.root_node();
|
||||
|
||||
// search for k nearest neighbors
|
||||
let collected = appstate.v2.dict[lang]
|
||||
.search(&target, top_k)
|
||||
.iter()
|
||||
.filter_map(|&index| {
|
||||
let applied = mutation::apply(
|
||||
langfn.clone(),
|
||||
source_bytes,
|
||||
root_node,
|
||||
&appstate.v2.mutations_collection[index],
|
||||
);
|
||||
match applied {
|
||||
Ok(v) => Some(v),
|
||||
Err(e) => {
|
||||
error!(
|
||||
collection_index = index,
|
||||
"failed to apply mutations from collection {}", e
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
// TODO: change the expect to a log
|
||||
})
|
||||
.collect();
|
||||
Ok(collected)
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
use actix_web::{
|
||||
HttpResponse, error,
|
||||
http::{StatusCode, header::ContentType},
|
||||
};
|
||||
use derive_more::derive::{Display, Error};
|
||||
use serde_json::json;
|
||||
|
||||
#[derive(Debug, Display, Error)]
|
||||
pub enum Error {
|
||||
#[display("the server is busy. come back later.")]
|
||||
Busy,
|
||||
#[display("end your request with ` in somelang`.")]
|
||||
MissingSuffix,
|
||||
#[display("failed to embed your prompt.")]
|
||||
EmbedFailed,
|
||||
#[display("snippets were requested for an unknown language")]
|
||||
UnknownLang,
|
||||
#[display("failed to parse corpus of code to apply mutation on")]
|
||||
SnippetParsing,
|
||||
}
|
||||
|
||||
impl error::ResponseError for Error {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
let message = json!({
|
||||
"message": self.to_string(),
|
||||
})
|
||||
.to_string();
|
||||
HttpResponse::build(self.status_code())
|
||||
.insert_header(ContentType::json())
|
||||
.body(message)
|
||||
}
|
||||
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {
|
||||
Self::EmbedFailed => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Self::MissingSuffix | Self::UnknownLang | Self::SnippetParsing => {
|
||||
StatusCode::BAD_REQUEST
|
||||
}
|
||||
Self::Busy => StatusCode::GATEWAY_TIMEOUT,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pub(crate) mod api;
|
||||
pub(crate) mod errors;
|
||||
pub(crate) mod mutation;
|
||||
Reference in New Issue
Block a user