├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── extension.toml ├── languages └── gleam │ ├── brackets.scm │ ├── config.toml │ ├── highlights.scm │ ├── indents.scm │ ├── outline.scm │ ├── runnables.scm │ └── tasks.json ├── packages.txt └── src ├── gleam.rs └── hexdocs.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # zed-gleam 3 | # 4 | 5 | *.wasm 6 | 7 | # 8 | # Rust 9 | # 10 | 11 | /target 12 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anyhow" 16 | version = "1.0.89" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" 19 | 20 | [[package]] 21 | name = "autocfg" 22 | version = "1.4.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 25 | 26 | [[package]] 27 | name = "bitflags" 28 | version = "2.6.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 31 | 32 | [[package]] 33 | name = "byteorder" 34 | version = "1.5.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 37 | 38 | [[package]] 39 | name = "cfg-if" 40 | version = "1.0.0" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 43 | 44 | [[package]] 45 | name = "equivalent" 46 | version = "1.0.1" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 49 | 50 | [[package]] 51 | name = "futf" 52 | version = "0.1.5" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" 55 | dependencies = [ 56 | "mac", 57 | "new_debug_unreachable", 58 | ] 59 | 60 | [[package]] 61 | name = "getrandom" 62 | version = "0.2.15" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 65 | dependencies = [ 66 | "cfg-if", 67 | "libc", 68 | "wasi", 69 | ] 70 | 71 | [[package]] 72 | name = "hashbrown" 73 | version = "0.15.0" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" 76 | 77 | [[package]] 78 | name = "heck" 79 | version = "0.4.1" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 82 | dependencies = [ 83 | "unicode-segmentation", 84 | ] 85 | 86 | [[package]] 87 | name = "html5ever" 88 | version = "0.27.0" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4" 91 | dependencies = [ 92 | "log", 93 | "mac", 94 | "markup5ever", 95 | "proc-macro2", 96 | "quote", 97 | "syn", 98 | ] 99 | 100 | [[package]] 101 | name = "html_to_markdown" 102 | version = "0.1.0" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "e608e8dd0939bfb6b516d96a5919751b835297a02230aecb88d2fc84ebebaa8a" 105 | dependencies = [ 106 | "anyhow", 107 | "html5ever", 108 | "markup5ever_rcdom", 109 | "regex", 110 | ] 111 | 112 | [[package]] 113 | name = "id-arena" 114 | version = "2.2.1" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" 117 | 118 | [[package]] 119 | name = "indexmap" 120 | version = "2.6.0" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" 123 | dependencies = [ 124 | "equivalent", 125 | "hashbrown", 126 | "serde", 127 | ] 128 | 129 | [[package]] 130 | name = "itoa" 131 | version = "1.0.11" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 134 | 135 | [[package]] 136 | name = "leb128" 137 | version = "0.2.5" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" 140 | 141 | [[package]] 142 | name = "libc" 143 | version = "0.2.159" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" 146 | 147 | [[package]] 148 | name = "lock_api" 149 | version = "0.4.12" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 152 | dependencies = [ 153 | "autocfg", 154 | "scopeguard", 155 | ] 156 | 157 | [[package]] 158 | name = "log" 159 | version = "0.4.22" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 162 | 163 | [[package]] 164 | name = "mac" 165 | version = "0.1.1" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" 168 | 169 | [[package]] 170 | name = "markup5ever" 171 | version = "0.12.1" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" 174 | dependencies = [ 175 | "log", 176 | "phf", 177 | "phf_codegen", 178 | "string_cache", 179 | "string_cache_codegen", 180 | "tendril", 181 | ] 182 | 183 | [[package]] 184 | name = "markup5ever_rcdom" 185 | version = "0.3.0" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "edaa21ab3701bfee5099ade5f7e1f84553fd19228cf332f13cd6e964bf59be18" 188 | dependencies = [ 189 | "html5ever", 190 | "markup5ever", 191 | "tendril", 192 | "xml5ever", 193 | ] 194 | 195 | [[package]] 196 | name = "memchr" 197 | version = "2.7.4" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 200 | 201 | [[package]] 202 | name = "new_debug_unreachable" 203 | version = "1.0.6" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" 206 | 207 | [[package]] 208 | name = "once_cell" 209 | version = "1.20.2" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 212 | 213 | [[package]] 214 | name = "parking_lot" 215 | version = "0.12.3" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 218 | dependencies = [ 219 | "lock_api", 220 | "parking_lot_core", 221 | ] 222 | 223 | [[package]] 224 | name = "parking_lot_core" 225 | version = "0.9.10" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 228 | dependencies = [ 229 | "cfg-if", 230 | "libc", 231 | "redox_syscall", 232 | "smallvec", 233 | "windows-targets", 234 | ] 235 | 236 | [[package]] 237 | name = "phf" 238 | version = "0.11.2" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" 241 | dependencies = [ 242 | "phf_shared 0.11.2", 243 | ] 244 | 245 | [[package]] 246 | name = "phf_codegen" 247 | version = "0.11.2" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" 250 | dependencies = [ 251 | "phf_generator 0.11.2", 252 | "phf_shared 0.11.2", 253 | ] 254 | 255 | [[package]] 256 | name = "phf_generator" 257 | version = "0.10.0" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" 260 | dependencies = [ 261 | "phf_shared 0.10.0", 262 | "rand", 263 | ] 264 | 265 | [[package]] 266 | name = "phf_generator" 267 | version = "0.11.2" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" 270 | dependencies = [ 271 | "phf_shared 0.11.2", 272 | "rand", 273 | ] 274 | 275 | [[package]] 276 | name = "phf_shared" 277 | version = "0.10.0" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" 280 | dependencies = [ 281 | "siphasher", 282 | ] 283 | 284 | [[package]] 285 | name = "phf_shared" 286 | version = "0.11.2" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" 289 | dependencies = [ 290 | "siphasher", 291 | ] 292 | 293 | [[package]] 294 | name = "ppv-lite86" 295 | version = "0.2.20" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 298 | dependencies = [ 299 | "zerocopy", 300 | ] 301 | 302 | [[package]] 303 | name = "precomputed-hash" 304 | version = "0.1.1" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" 307 | 308 | [[package]] 309 | name = "proc-macro2" 310 | version = "1.0.87" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" 313 | dependencies = [ 314 | "unicode-ident", 315 | ] 316 | 317 | [[package]] 318 | name = "quote" 319 | version = "1.0.37" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 322 | dependencies = [ 323 | "proc-macro2", 324 | ] 325 | 326 | [[package]] 327 | name = "rand" 328 | version = "0.8.5" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 331 | dependencies = [ 332 | "libc", 333 | "rand_chacha", 334 | "rand_core", 335 | ] 336 | 337 | [[package]] 338 | name = "rand_chacha" 339 | version = "0.3.1" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 342 | dependencies = [ 343 | "ppv-lite86", 344 | "rand_core", 345 | ] 346 | 347 | [[package]] 348 | name = "rand_core" 349 | version = "0.6.4" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 352 | dependencies = [ 353 | "getrandom", 354 | ] 355 | 356 | [[package]] 357 | name = "redox_syscall" 358 | version = "0.5.7" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" 361 | dependencies = [ 362 | "bitflags", 363 | ] 364 | 365 | [[package]] 366 | name = "regex" 367 | version = "1.11.0" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" 370 | dependencies = [ 371 | "aho-corasick", 372 | "memchr", 373 | "regex-automata", 374 | "regex-syntax", 375 | ] 376 | 377 | [[package]] 378 | name = "regex-automata" 379 | version = "0.4.8" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" 382 | dependencies = [ 383 | "aho-corasick", 384 | "memchr", 385 | "regex-syntax", 386 | ] 387 | 388 | [[package]] 389 | name = "regex-syntax" 390 | version = "0.8.5" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 393 | 394 | [[package]] 395 | name = "ryu" 396 | version = "1.0.18" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 399 | 400 | [[package]] 401 | name = "scopeguard" 402 | version = "1.2.0" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 405 | 406 | [[package]] 407 | name = "semver" 408 | version = "1.0.23" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" 411 | 412 | [[package]] 413 | name = "serde" 414 | version = "1.0.210" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" 417 | dependencies = [ 418 | "serde_derive", 419 | ] 420 | 421 | [[package]] 422 | name = "serde_derive" 423 | version = "1.0.210" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" 426 | dependencies = [ 427 | "proc-macro2", 428 | "quote", 429 | "syn", 430 | ] 431 | 432 | [[package]] 433 | name = "serde_json" 434 | version = "1.0.128" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" 437 | dependencies = [ 438 | "itoa", 439 | "memchr", 440 | "ryu", 441 | "serde", 442 | ] 443 | 444 | [[package]] 445 | name = "siphasher" 446 | version = "0.3.11" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" 449 | 450 | [[package]] 451 | name = "smallvec" 452 | version = "1.13.2" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 455 | 456 | [[package]] 457 | name = "spdx" 458 | version = "0.10.6" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "47317bbaf63785b53861e1ae2d11b80d6b624211d42cb20efcd210ee6f8a14bc" 461 | dependencies = [ 462 | "smallvec", 463 | ] 464 | 465 | [[package]] 466 | name = "string_cache" 467 | version = "0.8.7" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" 470 | dependencies = [ 471 | "new_debug_unreachable", 472 | "once_cell", 473 | "parking_lot", 474 | "phf_shared 0.10.0", 475 | "precomputed-hash", 476 | "serde", 477 | ] 478 | 479 | [[package]] 480 | name = "string_cache_codegen" 481 | version = "0.5.2" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" 484 | dependencies = [ 485 | "phf_generator 0.10.0", 486 | "phf_shared 0.10.0", 487 | "proc-macro2", 488 | "quote", 489 | ] 490 | 491 | [[package]] 492 | name = "syn" 493 | version = "2.0.79" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" 496 | dependencies = [ 497 | "proc-macro2", 498 | "quote", 499 | "unicode-ident", 500 | ] 501 | 502 | [[package]] 503 | name = "tendril" 504 | version = "0.4.3" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" 507 | dependencies = [ 508 | "futf", 509 | "mac", 510 | "utf-8", 511 | ] 512 | 513 | [[package]] 514 | name = "unicode-ident" 515 | version = "1.0.13" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 518 | 519 | [[package]] 520 | name = "unicode-segmentation" 521 | version = "1.12.0" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 524 | 525 | [[package]] 526 | name = "unicode-xid" 527 | version = "0.2.6" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 530 | 531 | [[package]] 532 | name = "utf-8" 533 | version = "0.7.6" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 536 | 537 | [[package]] 538 | name = "wasi" 539 | version = "0.11.0+wasi-snapshot-preview1" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 542 | 543 | [[package]] 544 | name = "wasm-encoder" 545 | version = "0.201.0" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "b9c7d2731df60006819b013f64ccc2019691deccf6e11a1804bc850cd6748f1a" 548 | dependencies = [ 549 | "leb128", 550 | ] 551 | 552 | [[package]] 553 | name = "wasm-metadata" 554 | version = "0.201.0" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "0fd83062c17b9f4985d438603cde0a5e8c5c8198201a6937f778b607924c7da2" 557 | dependencies = [ 558 | "anyhow", 559 | "indexmap", 560 | "serde", 561 | "serde_derive", 562 | "serde_json", 563 | "spdx", 564 | "wasm-encoder", 565 | "wasmparser", 566 | ] 567 | 568 | [[package]] 569 | name = "wasmparser" 570 | version = "0.201.0" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "84e5df6dba6c0d7fafc63a450f1738451ed7a0b52295d83e868218fa286bf708" 573 | dependencies = [ 574 | "bitflags", 575 | "indexmap", 576 | "semver", 577 | ] 578 | 579 | [[package]] 580 | name = "windows-targets" 581 | version = "0.52.6" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 584 | dependencies = [ 585 | "windows_aarch64_gnullvm", 586 | "windows_aarch64_msvc", 587 | "windows_i686_gnu", 588 | "windows_i686_gnullvm", 589 | "windows_i686_msvc", 590 | "windows_x86_64_gnu", 591 | "windows_x86_64_gnullvm", 592 | "windows_x86_64_msvc", 593 | ] 594 | 595 | [[package]] 596 | name = "windows_aarch64_gnullvm" 597 | version = "0.52.6" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 600 | 601 | [[package]] 602 | name = "windows_aarch64_msvc" 603 | version = "0.52.6" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 606 | 607 | [[package]] 608 | name = "windows_i686_gnu" 609 | version = "0.52.6" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 612 | 613 | [[package]] 614 | name = "windows_i686_gnullvm" 615 | version = "0.52.6" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 618 | 619 | [[package]] 620 | name = "windows_i686_msvc" 621 | version = "0.52.6" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 624 | 625 | [[package]] 626 | name = "windows_x86_64_gnu" 627 | version = "0.52.6" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 630 | 631 | [[package]] 632 | name = "windows_x86_64_gnullvm" 633 | version = "0.52.6" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 636 | 637 | [[package]] 638 | name = "windows_x86_64_msvc" 639 | version = "0.52.6" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 642 | 643 | [[package]] 644 | name = "wit-bindgen" 645 | version = "0.22.0" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "288f992ea30e6b5c531b52cdd5f3be81c148554b09ea416f058d16556ba92c27" 648 | dependencies = [ 649 | "bitflags", 650 | "wit-bindgen-rt", 651 | "wit-bindgen-rust-macro", 652 | ] 653 | 654 | [[package]] 655 | name = "wit-bindgen-core" 656 | version = "0.22.0" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "e85e72719ffbccf279359ad071497e47eb0675fe22106dea4ed2d8a7fcb60ba4" 659 | dependencies = [ 660 | "anyhow", 661 | "wit-parser", 662 | ] 663 | 664 | [[package]] 665 | name = "wit-bindgen-rt" 666 | version = "0.22.0" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "fcb8738270f32a2d6739973cbbb7c1b6dd8959ce515578a6e19165853272ee64" 669 | 670 | [[package]] 671 | name = "wit-bindgen-rust" 672 | version = "0.22.0" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "d8a39a15d1ae2077688213611209849cad40e9e5cccf6e61951a425850677ff3" 675 | dependencies = [ 676 | "anyhow", 677 | "heck", 678 | "indexmap", 679 | "wasm-metadata", 680 | "wit-bindgen-core", 681 | "wit-component", 682 | ] 683 | 684 | [[package]] 685 | name = "wit-bindgen-rust-macro" 686 | version = "0.22.0" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "d376d3ae5850526dfd00d937faea0d81a06fa18f7ac1e26f386d760f241a8f4b" 689 | dependencies = [ 690 | "anyhow", 691 | "proc-macro2", 692 | "quote", 693 | "syn", 694 | "wit-bindgen-core", 695 | "wit-bindgen-rust", 696 | ] 697 | 698 | [[package]] 699 | name = "wit-component" 700 | version = "0.201.0" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "421c0c848a0660a8c22e2fd217929a0191f14476b68962afd2af89fd22e39825" 703 | dependencies = [ 704 | "anyhow", 705 | "bitflags", 706 | "indexmap", 707 | "log", 708 | "serde", 709 | "serde_derive", 710 | "serde_json", 711 | "wasm-encoder", 712 | "wasm-metadata", 713 | "wasmparser", 714 | "wit-parser", 715 | ] 716 | 717 | [[package]] 718 | name = "wit-parser" 719 | version = "0.201.0" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "196d3ecfc4b759a8573bf86a9b3f8996b304b3732e4c7de81655f875f6efdca6" 722 | dependencies = [ 723 | "anyhow", 724 | "id-arena", 725 | "indexmap", 726 | "log", 727 | "semver", 728 | "serde", 729 | "serde_derive", 730 | "serde_json", 731 | "unicode-xid", 732 | "wasmparser", 733 | ] 734 | 735 | [[package]] 736 | name = "xml5ever" 737 | version = "0.18.1" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "9bbb26405d8e919bc1547a5aa9abc95cbfa438f04844f5fdd9dc7596b748bf69" 740 | dependencies = [ 741 | "log", 742 | "mac", 743 | "markup5ever", 744 | ] 745 | 746 | [[package]] 747 | name = "zed_extension_api" 748 | version = "0.1.0" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "594fd10dd0f2f853eb243e2425e7c95938cef49adb81d9602921d002c5e6d9d9" 751 | dependencies = [ 752 | "serde", 753 | "serde_json", 754 | "wit-bindgen", 755 | ] 756 | 757 | [[package]] 758 | name = "zed_gleam" 759 | version = "0.4.0" 760 | dependencies = [ 761 | "html_to_markdown", 762 | "zed_extension_api", 763 | ] 764 | 765 | [[package]] 766 | name = "zerocopy" 767 | version = "0.7.35" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 770 | dependencies = [ 771 | "byteorder", 772 | "zerocopy-derive", 773 | ] 774 | 775 | [[package]] 776 | name = "zerocopy-derive" 777 | version = "0.7.35" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 780 | dependencies = [ 781 | "proc-macro2", 782 | "quote", 783 | "syn", 784 | ] 785 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "zed_gleam" 3 | version = "0.4.0" 4 | edition = "2021" 5 | publish = false 6 | license = "Apache-2.0" 7 | 8 | [lib] 9 | path = "src/gleam.rs" 10 | crate-type = ["cdylib"] 11 | 12 | [dependencies] 13 | html_to_markdown = "0.1.0" 14 | zed_extension_api = "0.1.0" 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Unless required by applicable law or agreed to in writing, software 179 | distributed under the License is distributed on an "AS IS" BASIS, 180 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 181 | See the License for the specific language governing permissions and 182 | limitations under the License. 183 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zed Gleam 2 | 3 | A [Gleam](https://gleam.run/) extension for [Zed](https://zed.dev/). 4 | 5 | Provides syntax highlighting and use of the Gleam Language Server, which itself provides formatting, goto-definition, autocompletion, and more! 6 | 7 | ## Development 8 | 9 | To develop this extension, see the [Developing Extensions](https://zed.dev/docs/extensions/developing-extensions) section of the Zed docs. 10 | -------------------------------------------------------------------------------- /extension.toml: -------------------------------------------------------------------------------- 1 | id = "gleam" 2 | name = "Gleam" 3 | description = "Gleam support." 4 | version = "0.4.0" 5 | schema_version = 1 6 | authors = ["Marshall Bowers "] 7 | repository = "https://github.com/gleam-lang/zed-gleam" 8 | 9 | [language_servers.gleam] 10 | name = "Gleam LSP" 11 | language = "Gleam" 12 | 13 | [grammars.gleam] 14 | repository = "https://github.com/gleam-lang/tree-sitter-gleam" 15 | commit = "066704e4826699e754d351e3bbe12bf2e51de9d8" 16 | 17 | [slash_commands.gleam-project] 18 | description = "Returns information about the current Gleam project." 19 | requires_argument = false 20 | 21 | [indexed_docs_providers.gleam-hexdocs] 22 | -------------------------------------------------------------------------------- /languages/gleam/brackets.scm: -------------------------------------------------------------------------------- 1 | ("(" @open ")" @close) 2 | ("[" @open "]" @close) 3 | ("{" @open "}" @close) 4 | ("\"" @open "\"" @close) 5 | -------------------------------------------------------------------------------- /languages/gleam/config.toml: -------------------------------------------------------------------------------- 1 | name = "Gleam" 2 | grammar = "gleam" 3 | path_suffixes = ["gleam"] 4 | line_comments = ["// ", "/// "] 5 | autoclose_before = ";:.,=}])>" 6 | brackets = [ 7 | { start = "{", end = "}", close = true, newline = true }, 8 | { start = "[", end = "]", close = true, newline = true }, 9 | { start = "(", end = ")", close = true, newline = true }, 10 | { start = "\"", end = "\"", close = true, newline = false, not_in = ["string", "comment"] }, 11 | ] 12 | tab_size = 2 13 | -------------------------------------------------------------------------------- /languages/gleam/highlights.scm: -------------------------------------------------------------------------------- 1 | ; Comments 2 | (module_comment) @comment 3 | (statement_comment) @comment 4 | (comment) @comment 5 | 6 | ; Constants 7 | (constant 8 | name: (identifier) @constant) 9 | 10 | ; Variables 11 | (identifier) @variable 12 | (discard) @comment.unused 13 | 14 | ; Modules 15 | (module) @module 16 | (import alias: (identifier) @module) 17 | (remote_type_identifier 18 | module: (identifier) @module) 19 | (remote_constructor_name 20 | module: (identifier) @module) 21 | ((field_access 22 | record: (identifier) @module 23 | field: (label) @function) 24 | (#is-not? local)) 25 | 26 | ; Functions 27 | (unqualified_import (identifier) @function) 28 | (unqualified_import "type" (type_identifier) @type) 29 | (unqualified_import (type_identifier) @constructor) 30 | (function 31 | name: (identifier) @function) 32 | (external_function 33 | name: (identifier) @function) 34 | (function_parameter 35 | name: (identifier) @variable.parameter) 36 | ((function_call 37 | function: (identifier) @function) 38 | (#is-not? local)) 39 | ((binary_expression 40 | operator: "|>" 41 | right: (identifier) @function) 42 | (#is-not? local)) 43 | 44 | ; "Properties" 45 | ; Assumed to be intended to refer to a name for a field; something that comes 46 | ; before ":" or after "." 47 | ; e.g. record field names, tuple indices, names for named arguments, etc 48 | (label) @property 49 | (tuple_access 50 | index: (integer) @property) 51 | 52 | ; Attributes 53 | (attribute 54 | "@" @attribute 55 | name: (identifier) @attribute) 56 | 57 | (attribute_value (identifier) @constant) 58 | 59 | ; Type names 60 | (remote_type_identifier) @type 61 | (type_identifier) @type 62 | 63 | ; Data constructors 64 | (constructor_name) @constructor 65 | 66 | ; Literals 67 | (string) @string 68 | ((escape_sequence) @warning 69 | ; Deprecated in v0.33.0-rc2: 70 | (#eq? @warning "\\e")) 71 | (escape_sequence) @string.escape 72 | (bit_string_segment_option) @function.builtin 73 | (integer) @number 74 | (float) @number 75 | 76 | ; Reserved identifiers 77 | ; TODO: when tree-sitter supports `#any-of?` in the Rust bindings, 78 | ; refactor this to use `#any-of?` rather than `#match?` 79 | ((identifier) @warning 80 | (#match? @warning "^(auto|delegate|derive|else|implement|macro|test)$")) 81 | 82 | ; Keywords 83 | [ 84 | (visibility_modifier) ; "pub" 85 | (opacity_modifier) ; "opaque" 86 | "as" 87 | "assert" 88 | "case" 89 | "const" 90 | "echo" 91 | ; DEPRECATED: 'external' was removed in v0.30. 92 | "external" 93 | "fn" 94 | "if" 95 | "import" 96 | "let" 97 | "panic" 98 | "todo" 99 | "type" 100 | "use" 101 | ] @keyword 102 | 103 | ; Operators 104 | (binary_expression 105 | operator: _ @operator) 106 | (boolean_negation "!" @operator) 107 | (integer_negation "-" @operator) 108 | 109 | ; Punctuation 110 | [ 111 | "(" 112 | ")" 113 | "[" 114 | "]" 115 | "{" 116 | "}" 117 | "<<" 118 | ">>" 119 | ] @punctuation.bracket 120 | [ 121 | "." 122 | "," 123 | ;; Controversial -- maybe some are operators? 124 | ":" 125 | "#" 126 | "=" 127 | "->" 128 | ".." 129 | "-" 130 | "<-" 131 | ] @punctuation.delimiter 132 | -------------------------------------------------------------------------------- /languages/gleam/indents.scm: -------------------------------------------------------------------------------- 1 | (_ "[" "]" @end) @indent 2 | (_ "{" "}" @end) @indent 3 | (_ "(" ")" @end) @indent 4 | -------------------------------------------------------------------------------- /languages/gleam/outline.scm: -------------------------------------------------------------------------------- 1 | (external_type 2 | (visibility_modifier)? @context 3 | "type" @context 4 | (type_name) @name) @item 5 | 6 | (type_definition 7 | (visibility_modifier)? @context 8 | (opacity_modifier)? @context 9 | "type" @context 10 | (type_name) @name) @item 11 | 12 | (data_constructor 13 | (constructor_name) @name) @item 14 | 15 | (data_constructor_argument 16 | (label) @name) @item 17 | 18 | (type_alias 19 | (visibility_modifier)? @context 20 | "type" @context 21 | (type_name) @name) @item 22 | 23 | (function 24 | (visibility_modifier)? @context 25 | "fn" @context 26 | name: (_) @name) @item 27 | 28 | (constant 29 | (visibility_modifier)? @context 30 | "const" @context 31 | name: (_) @name) @item 32 | 33 | (statement_comment) @annotation 34 | -------------------------------------------------------------------------------- /languages/gleam/runnables.scm: -------------------------------------------------------------------------------- 1 | ; Functions with names ending in `_test`. 2 | ; This matches the standalone test style used by Startest and Gleeunit. 3 | ( 4 | ( 5 | (function name: (_) @run 6 | (#match? @run ".*_test$")) 7 | ) @gleam-test 8 | (#set! tag gleam-test) 9 | ) 10 | 11 | 12 | ; `describe` API for Startest. 13 | ( 14 | (function_call 15 | function: (_) @name 16 | (#any-of? @name "describe" "it") 17 | arguments: (arguments 18 | . 19 | (argument 20 | value: (string (quoted_content) @run) 21 | ) 22 | ) 23 | ) 24 | (#set! tag gleam-test) 25 | ) @gleam-test 26 | -------------------------------------------------------------------------------- /languages/gleam/tasks.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "label": "gleam test", 4 | "command": "gleam", 5 | "args": ["test"] 6 | }, 7 | { 8 | "label": "gleam test $ZED_SYMBOL", 9 | "command": "gleam", 10 | "args": ["test", "--", "--test-name-filter=$ZED_SYMBOL"], 11 | "tags": ["gleam-test"] 12 | } 13 | ] 14 | -------------------------------------------------------------------------------- /packages.txt: -------------------------------------------------------------------------------- 1 | # The list of Gleam packages. 2 | # Sourced from `https://packages.gleam.run/packages.sqlite`. 3 | act 4 | adglent 5 | ag_html 6 | aham 7 | akaridb 8 | alanttest1 9 | alpaca 10 | amf0 11 | amnesiac 12 | antigone 13 | apollo 14 | aragorn2 15 | arcana_signals 16 | arctic 17 | argamak 18 | argus 19 | argv 20 | ask 21 | asterix 22 | atomic_array 23 | aws4_request 24 | bare_package1 25 | bare_package_one 26 | bare_package_two 27 | based 28 | based_pg 29 | based_sqlite 30 | beecrypt 31 | bidict 32 | bigben 33 | bigi 34 | binary_search 35 | birdie 36 | birl 37 | biscotto 38 | bison 39 | blah 40 | blask 41 | bliss 42 | bravo 43 | bungle 44 | bytesize 45 | cactus 46 | cake 47 | carpenter 48 | catppuccin 49 | cave3dplus 50 | cgi 51 | chatbot 52 | check_maybe_div_by_zero 53 | chip 54 | chomp 55 | chrobot 56 | chromatic 57 | classify 58 | cleam 59 | collatz 60 | colored 61 | colours 62 | comet 63 | commonmark 64 | conllu 65 | context_fp_gleam 66 | conversation 67 | cors_builder 68 | cosepo 69 | cosmos 70 | counter 71 | crabbucket_pgo 72 | crabbucket_redis 73 | crossbar 74 | css_select 75 | cymbal 76 | dahlia 77 | dbots 78 | decepticon 79 | decipher 80 | decode 81 | dedent 82 | defangle 83 | defer_g 84 | delay 85 | dew 86 | dig 87 | discord_gleam 88 | domu 89 | dot_env 90 | dotenv_gleam 91 | dove 92 | ecoji 93 | edit_distance 94 | efetch 95 | email 96 | embeds 97 | emel 98 | envoy 99 | esgleam 100 | espresso 101 | espresso_pgo_wrapper 102 | eval 103 | event_hub 104 | eventsourcing 105 | eventsourcing_postgres 106 | eventsourcing_sqlite 107 | exception 108 | exercism_test_runner 109 | facet 110 | facquest 111 | falala 112 | falcon 113 | feather 114 | fetch_event 115 | ffmpeg 116 | fibo 117 | file_streams 118 | filepath 119 | filespy 120 | finch_gleam 121 | first_gleam_publish_package 122 | flash 123 | fluoresce 124 | fmglee 125 | fmt 126 | for_the_crows 127 | form_coder 128 | formal 129 | fp 130 | fp2 131 | fp2_gleam 132 | fp_gl 133 | fresnel 134 | fswalk 135 | functx 136 | funtil 137 | gacache 138 | galant 139 | gap 140 | garnet_tool 141 | gary 142 | gbase32_clockwork 143 | gcalc 144 | gchess 145 | gemqtt 146 | gen_core_erlang 147 | gen_gleam 148 | geny 149 | germinal 150 | ggleam 151 | gild 152 | gild_frontend 153 | gip 154 | gjwt 155 | gl 156 | glacier 157 | glacier_gleeunit 158 | gladvent 159 | glailglind 160 | glam 161 | glame 162 | glaml 163 | glance 164 | glance_printer 165 | glanoid 166 | glare 167 | glatch 168 | glats 169 | glatus 170 | glcode 171 | gleaf 172 | gleam 173 | gleam_bbmustache 174 | gleam_bitwise 175 | gleam_bson 176 | gleam_community_ansi 177 | gleam_community_colour 178 | gleam_community_maths 179 | gleam_community_path 180 | gleam_cors 181 | gleam_cowboy 182 | gleam_cowboy_websockets 183 | gleam_crypto 184 | gleam_deno 185 | gleam_dotenv 186 | gleam_elli 187 | gleam_email 188 | gleam_erlang 189 | gleam_erlexec 190 | gleam_fetch 191 | gleam_gun 192 | gleam_hackney 193 | gleam_hexpm 194 | gleam_html 195 | gleam_http 196 | gleam_httpc 197 | gleam_javascript 198 | gleam_json 199 | gleam_module_javascript_test 200 | gleam_mongo 201 | gleam_nodejs 202 | gleam_os_mon 203 | gleam_otp 204 | gleam_package_interface 205 | gleam_pgo 206 | gleam_qs 207 | gleam_sendgrid 208 | gleam_stats 209 | gleam_stdlib 210 | gleam_synapses 211 | gleam_tailwind 212 | gleam_tcp 213 | gleam_test 214 | gleam_toml 215 | gleam_xml 216 | gleam_yaml 217 | gleam_zlists 218 | gleambox 219 | gleamix 220 | gleamql 221 | gleamsver 222 | gleamy_bench 223 | gleamy_structures 224 | gleamyshell 225 | gleanix 226 | glearray 227 | gleastsq 228 | gleative 229 | gleb128 230 | glector 231 | gledis 232 | gledo 233 | gleebor 234 | gleenix 235 | gleepl 236 | gleescript 237 | gleesend 238 | gleeunit 239 | gleez 240 | gleither 241 | glemini 242 | glemo 243 | glemplate 244 | glemtext 245 | glen 246 | glen_node 247 | glency 248 | glentities 249 | glenv 250 | glenvy 251 | glerd 252 | glerd_json 253 | glerd_valid 254 | glerm 255 | gleroglero 256 | glesha 257 | glesha2 258 | glevatar 259 | glevenshtein 260 | glex 261 | glexec 262 | glexer 263 | glexif 264 | glib 265 | gliberapay 266 | glibsql 267 | gliew 268 | glimiter 269 | glimmer 270 | glimt 271 | gling 272 | glint 273 | glisbn 274 | glisdigit 275 | glisten 276 | glistix_gleeunit 277 | glistix_nix 278 | glitch 279 | glitter 280 | glittr 281 | glitzer 282 | gliua 283 | globe 284 | glog 285 | glome 286 | gloml 287 | glomp 288 | gloom 289 | glormat 290 | gloss 291 | glotel 292 | glove 293 | glow 294 | glow_auth 295 | glubs 296 | glubsub 297 | glucose 298 | glue 299 | gluid 300 | gluon 301 | gluple 302 | glv8 303 | glx 304 | glychee 305 | glyph 306 | glyph_codegen 307 | glzoneinfo 308 | gmysql 309 | go_over 310 | gopenai 311 | gpsd_json 312 | gpxb 313 | grammy 314 | gramps 315 | graph 316 | grille_pain 317 | gripe 318 | gserde 319 | gstripe 320 | gsv 321 | gtempo 322 | gts 323 | gtui 324 | gu 325 | gwitch 326 | gwr 327 | gwt 328 | gxid 329 | gzlib 330 | halo 331 | handles 332 | hardcache 333 | hello_joe 334 | howdy 335 | howdy_authentication_cookies 336 | howdy_uuid 337 | htmb 338 | htmgrrrl 339 | html_components 340 | html_dsl 341 | html_lustre_converter 342 | html_parser 343 | htmz 344 | httpp 345 | hug 346 | humanise 347 | hyphenation 348 | ids 349 | ieee_float 350 | illustrious 351 | immutable_lru 352 | integer_complexity 353 | ior 354 | iox 355 | iso_8859 356 | ivy 357 | jackson 358 | jasper 359 | javascript_dom_parser 360 | jbs 361 | jot 362 | json_canvas 363 | juno 364 | justin 365 | keccak_gleam 366 | kick 367 | kielet 368 | kirala_bbmarkdown 369 | kirala_l4u 370 | kirala_markdown 371 | kreator 372 | libsql 373 | lite_fs 374 | logging 375 | lotta 376 | lumi 377 | lustre 378 | lustre_animation 379 | lustre_carousel 380 | lustre_dev_tools 381 | lustre_hash_state 382 | lustre_http 383 | lustre_hx 384 | lustre_limiter 385 | lustre_routed 386 | lustre_ssg 387 | lustre_transition 388 | lustre_ui 389 | lustre_virtual_list 390 | lustre_websocket 391 | lzf_gleam 392 | marceau 393 | mat 394 | meadow 395 | melon 396 | midas 397 | migrant 398 | mineflayer 399 | minigen 400 | mist 401 | mockth 402 | modem 403 | monies 404 | morse_code_translator 405 | mote 406 | mug 407 | mumu 408 | mungo 409 | nakai 410 | nanoworker 411 | nbeet 412 | nerf 413 | nessie 414 | nessie_cluster 415 | ngs 416 | nibble 417 | nimiq_gleam 418 | node_socket_client 419 | node_tags 420 | non_empty_list 421 | novdom 422 | novdom_dev_tools 423 | novdom_testing 424 | observatory 425 | open_color 426 | openfeature 427 | opt_args_with_defs_for_gleam 428 | oteap 429 | outcome 430 | outil 431 | owoify_gleam 432 | p5js_gleam 433 | palindrome 434 | panel 435 | parallel_map 436 | parser_gleam 437 | party 438 | parz 439 | pb_lite 440 | pears 441 | peggy 442 | phonetic_gleam 443 | phony 444 | phosphor_lustre 445 | pickle 446 | pika_id 447 | pine 448 | pink 449 | plex_pin_auth 450 | plinth 451 | plunk 452 | pngleam 453 | pojo 454 | pona 455 | postgresql_protocol 456 | pprint 457 | prequel 458 | pretty_diff 459 | priorityq 460 | prng 461 | process_groups 462 | process_waiter 463 | processgroups 464 | promgleam 465 | psg 466 | puddle 467 | punycode 468 | qcheck 469 | qcheck_gleeunit_utils 470 | qs 471 | queryb 472 | question 473 | rad 474 | rada 475 | radiate 476 | radish 477 | radish_fork 478 | ramble 479 | ranged_int 480 | ranger 481 | rank 482 | react_gleam 483 | reactive_signal 484 | ream 485 | recursive 486 | redraw 487 | redraw_dom 488 | ref 489 | rememo 490 | remote_data 491 | render_md 492 | repeatedly 493 | rizzo 494 | runetracer 495 | scaffold_gleam 496 | scriptorium 497 | sequin 498 | shakespeare 499 | shamir 500 | sheen 501 | shellout 502 | shimmer 503 | showtime 504 | signal 505 | signal_pgo 506 | simple_pubsub 507 | simplifile 508 | singularity 509 | sketch 510 | sketch_css 511 | sketch_lustre 512 | slackin 513 | snag 514 | snowgleam 515 | sol 516 | sparkle 517 | spinner 518 | sprinkle 519 | sprocket 520 | sqlight 521 | squirrel 522 | stacky 523 | staff_ai 524 | starmap 525 | startest 526 | stdin 527 | stego 528 | stoiridh_version 529 | storch 530 | stratus 531 | string_format 532 | sturnidae 533 | sunny 534 | surreal_gleam 535 | survey 536 | swen_jwt 537 | systemd_status 538 | tardis 539 | tcpea 540 | telega 541 | temporary 542 | term_size 543 | testbldr 544 | testcontainers_gleam 545 | the_stars 546 | tinyroute 547 | tom 548 | tote 549 | translate 550 | transparent_http 551 | trie_again 552 | trust 553 | tubes 554 | tulip 555 | tupler 556 | typed_headers 557 | valid 558 | validate_monadic 559 | varasto 560 | vindaloo 561 | vleam 562 | wasmify 563 | weapp 564 | webls 565 | webmidi 566 | wechat_dev_tools 567 | wemote 568 | wimp 569 | wink 570 | wisp 571 | wisp_flash 572 | wolf 573 | worm 574 | wp_tables 575 | xmb 576 | xmleam 577 | xmlm 578 | ygleam 579 | youid 580 | zeptomail 581 | zip_list 582 | -------------------------------------------------------------------------------- /src/gleam.rs: -------------------------------------------------------------------------------- 1 | mod hexdocs; 2 | 3 | use std::fs; 4 | use std::sync::LazyLock; 5 | use zed::lsp::CompletionKind; 6 | use zed::{ 7 | CodeLabel, CodeLabelSpan, KeyValueStore, LanguageServerId, SlashCommand, SlashCommandOutput, 8 | SlashCommandOutputSection, 9 | }; 10 | use zed_extension_api::{self as zed, Result}; 11 | 12 | struct GleamExtension { 13 | cached_binary_path: Option, 14 | } 15 | 16 | impl GleamExtension { 17 | fn language_server_binary_path( 18 | &mut self, 19 | language_server_id: &LanguageServerId, 20 | worktree: &zed::Worktree, 21 | ) -> Result { 22 | if let Some(path) = worktree.which("gleam") { 23 | return Ok(path); 24 | } 25 | 26 | if let Some(path) = &self.cached_binary_path { 27 | if fs::metadata(path).map_or(false, |stat| stat.is_file()) { 28 | return Ok(path.clone()); 29 | } 30 | } 31 | 32 | zed::set_language_server_installation_status( 33 | language_server_id, 34 | &zed::LanguageServerInstallationStatus::CheckingForUpdate, 35 | ); 36 | let release = zed::latest_github_release( 37 | "gleam-lang/gleam", 38 | zed::GithubReleaseOptions { 39 | require_assets: true, 40 | pre_release: false, 41 | }, 42 | )?; 43 | 44 | let (platform, arch) = zed::current_platform(); 45 | let asset_name = format!( 46 | "gleam-{version}-{arch}-{os}.{ext}", 47 | version = release.version, 48 | arch = match arch { 49 | zed::Architecture::Aarch64 => "aarch64", 50 | zed::Architecture::X86 => "x86", 51 | zed::Architecture::X8664 => "x86_64", 52 | }, 53 | os = match platform { 54 | zed::Os::Mac => "apple-darwin", 55 | zed::Os::Linux => "unknown-linux-musl", 56 | zed::Os::Windows => "pc-windows-msvc", 57 | }, 58 | ext = match platform { 59 | zed::Os::Mac | zed::Os::Linux => "tar.gz", 60 | zed::Os::Windows => "zip", 61 | }, 62 | ); 63 | 64 | let asset = release 65 | .assets 66 | .iter() 67 | .find(|asset| asset.name == asset_name) 68 | .ok_or_else(|| format!("no asset found matching {:?}", asset_name))?; 69 | 70 | let version_dir = format!("gleam-{}", release.version); 71 | let binary_path = format!("{version_dir}/gleam"); 72 | 73 | if !fs::metadata(&binary_path).map_or(false, |stat| stat.is_file()) { 74 | zed::set_language_server_installation_status( 75 | language_server_id, 76 | &zed::LanguageServerInstallationStatus::Downloading, 77 | ); 78 | 79 | zed::download_file( 80 | &asset.download_url, 81 | &version_dir, 82 | match platform { 83 | zed::Os::Mac | zed::Os::Linux => zed::DownloadedFileType::GzipTar, 84 | zed::Os::Windows => zed::DownloadedFileType::Zip, 85 | }, 86 | ) 87 | .map_err(|e| format!("failed to download file: {e}"))?; 88 | 89 | let entries = 90 | fs::read_dir(".").map_err(|e| format!("failed to list working directory {e}"))?; 91 | for entry in entries { 92 | let entry = entry.map_err(|e| format!("failed to load directory entry {e}"))?; 93 | if entry.file_name().to_str() != Some(&version_dir) { 94 | fs::remove_dir_all(entry.path()).ok(); 95 | } 96 | } 97 | } 98 | 99 | self.cached_binary_path = Some(binary_path.clone()); 100 | Ok(binary_path) 101 | } 102 | } 103 | 104 | impl zed::Extension for GleamExtension { 105 | fn new() -> Self { 106 | Self { 107 | cached_binary_path: None, 108 | } 109 | } 110 | 111 | fn language_server_command( 112 | &mut self, 113 | language_server_id: &LanguageServerId, 114 | worktree: &zed::Worktree, 115 | ) -> Result { 116 | Ok(zed::Command { 117 | command: self.language_server_binary_path(language_server_id, worktree)?, 118 | args: vec!["lsp".to_string()], 119 | env: Default::default(), 120 | }) 121 | } 122 | 123 | fn label_for_completion( 124 | &self, 125 | _language_server_id: &LanguageServerId, 126 | completion: zed::lsp::Completion, 127 | ) -> Option { 128 | let name = &completion.label; 129 | let ty = strip_newlines_from_detail(&completion.detail?); 130 | let let_binding = "let a"; 131 | let colon = ": "; 132 | let assignment = " = "; 133 | let call = match completion.kind? { 134 | CompletionKind::Function | CompletionKind::Constructor => "()", 135 | _ => "", 136 | }; 137 | let code = format!("{let_binding}{colon}{ty}{assignment}{name}{call}"); 138 | 139 | Some(CodeLabel { 140 | spans: vec![ 141 | CodeLabelSpan::code_range({ 142 | let start = let_binding.len() + colon.len() + ty.len() + assignment.len(); 143 | start..start + name.len() 144 | }), 145 | CodeLabelSpan::code_range({ 146 | let start = let_binding.len(); 147 | start..start + colon.len() 148 | }), 149 | CodeLabelSpan::code_range({ 150 | let start = let_binding.len() + colon.len(); 151 | start..start + ty.len() 152 | }), 153 | ], 154 | filter_range: (0..name.len()).into(), 155 | code, 156 | }) 157 | } 158 | 159 | fn run_slash_command( 160 | &self, 161 | command: SlashCommand, 162 | _args: Vec, 163 | worktree: Option<&zed::Worktree>, 164 | ) -> Result { 165 | match command.name.as_str() { 166 | "gleam-project" => { 167 | let worktree = worktree.ok_or("no worktree")?; 168 | 169 | let mut text = String::new(); 170 | text.push_str("You are in a Gleam project.\n"); 171 | 172 | if let Ok(gleam_toml) = worktree.read_text_file("gleam.toml") { 173 | text.push_str("The `gleam.toml` is as follows:\n"); 174 | text.push_str(&gleam_toml); 175 | } 176 | 177 | Ok(SlashCommandOutput { 178 | sections: vec![SlashCommandOutputSection { 179 | range: (0..text.len()).into(), 180 | label: "gleam-project".to_string(), 181 | }], 182 | text, 183 | }) 184 | } 185 | command => Err(format!("unknown slash command: \"{command}\"")), 186 | } 187 | } 188 | 189 | fn suggest_docs_packages(&self, provider: String) -> Result, String> { 190 | match provider.as_str() { 191 | "gleam-hexdocs" => { 192 | static GLEAM_PACKAGES: LazyLock> = LazyLock::new(|| { 193 | include_str!("../packages.txt") 194 | .lines() 195 | .filter(|line| !line.starts_with('#')) 196 | .map(|line| line.trim().to_owned()) 197 | .collect() 198 | }); 199 | 200 | Ok(GLEAM_PACKAGES.clone()) 201 | } 202 | _ => Ok(Vec::new()), 203 | } 204 | } 205 | 206 | fn index_docs( 207 | &self, 208 | provider: String, 209 | package: String, 210 | database: &KeyValueStore, 211 | ) -> Result<(), String> { 212 | match provider.as_str() { 213 | "gleam-hexdocs" => hexdocs::index(package, database), 214 | _ => Ok(()), 215 | } 216 | } 217 | } 218 | 219 | zed::register_extension!(GleamExtension); 220 | 221 | /// Removes newlines from the completion detail. 222 | /// 223 | /// The Gleam LSP can return types containing newlines, which causes formatting 224 | /// issues within the Zed completions menu. 225 | fn strip_newlines_from_detail(detail: &str) -> String { 226 | let without_newlines = detail 227 | .replace("->\n ", "-> ") 228 | .replace("\n ", "") 229 | .replace(",\n", ""); 230 | 231 | let comma_delimited_parts = without_newlines.split(','); 232 | comma_delimited_parts 233 | .map(|part| part.trim()) 234 | .collect::>() 235 | .join(", ") 236 | } 237 | 238 | #[cfg(test)] 239 | mod tests { 240 | use crate::strip_newlines_from_detail; 241 | 242 | #[test] 243 | fn test_strip_newlines_from_detail() { 244 | let detail = "fn(\n Selector(a),\n b,\n fn(Dynamic, Dynamic, Dynamic, Dynamic, Dynamic, Dynamic, Dynamic) -> a,\n) -> Selector(a)"; 245 | let expected = "fn(Selector(a), b, fn(Dynamic, Dynamic, Dynamic, Dynamic, Dynamic, Dynamic, Dynamic) -> a) -> Selector(a)"; 246 | assert_eq!(strip_newlines_from_detail(detail), expected); 247 | 248 | let detail = "fn(Selector(a), b, fn(Dynamic, Dynamic, Dynamic, Dynamic, Dynamic, Dynamic) -> a) ->\n Selector(a)"; 249 | let expected = "fn(Selector(a), b, fn(Dynamic, Dynamic, Dynamic, Dynamic, Dynamic, Dynamic) -> a) -> Selector(a)"; 250 | assert_eq!(strip_newlines_from_detail(detail), expected); 251 | 252 | let detail = "fn(\n Method,\n List(#(String, String)),\n a,\n Scheme,\n String,\n Option(Int),\n String,\n Option(String),\n) -> Request(a)"; 253 | let expected = "fn(Method, List(#(String, String)), a, Scheme, String, Option(Int), String, Option(String)) -> Request(a)"; 254 | assert_eq!(strip_newlines_from_detail(detail), expected); 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /src/hexdocs.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::collections::BTreeSet; 3 | use std::io::{self, Read}; 4 | use std::rc::Rc; 5 | 6 | use html_to_markdown::markdown::{ 7 | HeadingHandler, ListHandler, ParagraphHandler, StyledTextHandler, TableHandler, 8 | }; 9 | use html_to_markdown::{ 10 | convert_html_to_markdown, HandleTag, HandlerOutcome, HtmlElement, MarkdownWriter, 11 | StartTagOutcome, TagHandler, 12 | }; 13 | use zed_extension_api::{ 14 | http_client::{HttpMethod, HttpRequest, RedirectPolicy}, 15 | KeyValueStore, Result, 16 | }; 17 | 18 | pub fn index(package: String, database: &KeyValueStore) -> Result<()> { 19 | let headers = vec![( 20 | "User-Agent".to_string(), 21 | "Zed (Gleam Extension)".to_string(), 22 | )]; 23 | 24 | let response = HttpRequest::builder() 25 | .method(HttpMethod::Get) 26 | .url(format!("https://hexdocs.pm/{package}")) 27 | .headers(headers.clone()) 28 | .redirect_policy(RedirectPolicy::FollowAll) 29 | .build()? 30 | .fetch()?; 31 | 32 | let (package_root_markdown, modules) = 33 | convert_hexdocs_to_markdown(&mut io::Cursor::new(&response.body))?; 34 | 35 | database.insert(&package, &package_root_markdown)?; 36 | 37 | for module in modules { 38 | let response = HttpRequest::builder() 39 | .method(HttpMethod::Get) 40 | .url(format!("https://hexdocs.pm/{package}/{module}.html")) 41 | .headers(headers.clone()) 42 | .redirect_policy(RedirectPolicy::FollowAll) 43 | .build()? 44 | .fetch()?; 45 | 46 | let (markdown, _modules) = 47 | convert_hexdocs_to_markdown(&mut io::Cursor::new(&response.body))?; 48 | 49 | database.insert(&format!("{module} ({package})"), &markdown)?; 50 | } 51 | 52 | Ok(()) 53 | } 54 | 55 | pub fn convert_hexdocs_to_markdown(html: impl Read) -> Result<(String, Vec)> { 56 | let module_collector = Rc::new(RefCell::new(GleamModuleCollector::new())); 57 | 58 | let mut handlers: Vec = vec![ 59 | module_collector.clone(), 60 | Rc::new(RefCell::new(GleamChromeRemover)), 61 | Rc::new(RefCell::new(NavSkipper::new(ParagraphHandler))), 62 | Rc::new(RefCell::new(NavSkipper::new(HeadingHandler))), 63 | Rc::new(RefCell::new(NavSkipper::new(ListHandler))), 64 | Rc::new(RefCell::new(NavSkipper::new(TableHandler::new()))), 65 | Rc::new(RefCell::new(NavSkipper::new(StyledTextHandler))), 66 | ]; 67 | 68 | let markdown = convert_html_to_markdown(html, &mut handlers) 69 | .map_err(|err| format!("failed to convert docs to Markdown {err}"))?; 70 | 71 | let modules = module_collector 72 | .borrow() 73 | .modules 74 | .iter() 75 | .cloned() 76 | .collect::>(); 77 | 78 | Ok((markdown, modules)) 79 | } 80 | 81 | /// A higher-order handler that skips all content from the `nav`. 82 | /// 83 | /// We still need to traverse the `nav` for collecting information, but 84 | /// we don't want to include any of its content in the resulting Markdown. 85 | pub struct NavSkipper { 86 | handler: T, 87 | } 88 | 89 | impl NavSkipper { 90 | pub fn new(handler: T) -> Self { 91 | Self { handler } 92 | } 93 | } 94 | 95 | impl HandleTag for NavSkipper { 96 | fn should_handle(&self, tag: &str) -> bool { 97 | tag == "nav" || self.handler.should_handle(tag) 98 | } 99 | 100 | fn handle_tag_start( 101 | &mut self, 102 | tag: &HtmlElement, 103 | writer: &mut MarkdownWriter, 104 | ) -> StartTagOutcome { 105 | if writer.is_inside("nav") { 106 | return StartTagOutcome::Continue; 107 | } 108 | 109 | self.handler.handle_tag_start(tag, writer) 110 | } 111 | 112 | fn handle_tag_end(&mut self, tag: &HtmlElement, writer: &mut MarkdownWriter) { 113 | if writer.is_inside("nav") { 114 | return; 115 | } 116 | 117 | self.handler.handle_tag_end(tag, writer) 118 | } 119 | 120 | fn handle_text(&mut self, text: &str, writer: &mut MarkdownWriter) -> HandlerOutcome { 121 | if writer.is_inside("nav") { 122 | return HandlerOutcome::Handled; 123 | } 124 | 125 | self.handler.handle_text(text, writer) 126 | } 127 | } 128 | 129 | pub struct GleamChromeRemover; 130 | 131 | impl HandleTag for GleamChromeRemover { 132 | fn should_handle(&self, tag: &str) -> bool { 133 | matches!( 134 | tag, 135 | "head" | "script" | "style" | "svg" | "header" | "footer" | "a" 136 | ) 137 | } 138 | 139 | fn handle_tag_start( 140 | &mut self, 141 | tag: &HtmlElement, 142 | _writer: &mut MarkdownWriter, 143 | ) -> StartTagOutcome { 144 | match tag.tag() { 145 | "head" | "script" | "style" | "svg" | "header" | "footer" => { 146 | return StartTagOutcome::Skip; 147 | } 148 | "a" => { 149 | if tag.attr("onclick").is_some() { 150 | return StartTagOutcome::Skip; 151 | } 152 | } 153 | _ => {} 154 | } 155 | 156 | StartTagOutcome::Continue 157 | } 158 | } 159 | 160 | pub struct GleamModuleCollector { 161 | modules: BTreeSet, 162 | has_seen_modules_header: bool, 163 | } 164 | 165 | impl GleamModuleCollector { 166 | pub fn new() -> Self { 167 | Self { 168 | modules: BTreeSet::new(), 169 | has_seen_modules_header: false, 170 | } 171 | } 172 | 173 | fn parse_module(tag: &HtmlElement) -> Option { 174 | if tag.tag() != "a" { 175 | return None; 176 | } 177 | 178 | let href = tag.attr("href")?; 179 | if href.starts_with('#') || href.starts_with("https://") || href.starts_with("../") { 180 | return None; 181 | } 182 | 183 | let module_name = href.trim_start_matches("./").trim_end_matches(".html"); 184 | 185 | Some(module_name.to_owned()) 186 | } 187 | } 188 | 189 | impl HandleTag for GleamModuleCollector { 190 | fn should_handle(&self, tag: &str) -> bool { 191 | matches!(tag, "h2" | "a") 192 | } 193 | 194 | fn handle_tag_start( 195 | &mut self, 196 | tag: &HtmlElement, 197 | writer: &mut MarkdownWriter, 198 | ) -> StartTagOutcome { 199 | if tag.tag() == "a" && self.has_seen_modules_header && writer.is_inside("li") { 200 | if let Some(module_name) = Self::parse_module(tag) { 201 | self.modules.insert(module_name); 202 | } 203 | } 204 | 205 | StartTagOutcome::Continue 206 | } 207 | 208 | fn handle_text(&mut self, text: &str, writer: &mut MarkdownWriter) -> HandlerOutcome { 209 | if writer.is_inside("nav") && writer.is_inside("h2") && text == "Modules" { 210 | self.has_seen_modules_header = true; 211 | } 212 | 213 | HandlerOutcome::NoOp 214 | } 215 | } 216 | --------------------------------------------------------------------------------