Merge remote-tracking branch 'origin/master' into goto_next_reference

pull/6465/head
Anthony Templeton 10 months ago
commit 0190c48d41

296
Cargo.lock generated

@ -145,12 +145,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.84" version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856" checksum = "9b918671670962b48bc23753aef0c51d072dca6f52f01f800854ada6ddb7f7d3"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -171,9 +168,9 @@ dependencies = [
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.32" version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41daef31d7a747c5c847246f36de49ced6f7403b4cdabc807a97b5cc184cda7a" checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb"
dependencies = [ dependencies = [
"android-tzdata", "android-tzdata",
"iana-time-zone", "iana-time-zone",
@ -183,9 +180,9 @@ dependencies = [
[[package]] [[package]]
name = "clipboard-win" name = "clipboard-win"
version = "5.0.0" version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c57002a5d9be777c1ef967e33674dac9ebd310d8893e4e3437b14d5f0f6372cc" checksum = "3ec832972fefb8cf9313b45a0d1945e29c9c251f1d4c6eafc5fe2124c02d2e81"
dependencies = [ dependencies = [
"error-code", "error-code",
] ]
@ -424,9 +421,9 @@ dependencies = [
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.0.0" version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
[[package]] [[package]]
name = "fern" name = "fern"
@ -448,6 +445,18 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "filetime"
version = "0.2.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.4.1",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.0.27" version = "1.0.27"
@ -527,33 +536,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31887c304d9a935f3e5494fb5d6a0106c34e965168ec0db9b457424eedd0c741" checksum = "31887c304d9a935f3e5494fb5d6a0106c34e965168ec0db9b457424eedd0c741"
dependencies = [ dependencies = [
"gix-actor", "gix-actor",
"gix-attributes",
"gix-command",
"gix-commitgraph", "gix-commitgraph",
"gix-config", "gix-config",
"gix-date", "gix-date",
"gix-diff", "gix-diff",
"gix-discover", "gix-discover",
"gix-features", "gix-features",
"gix-filter",
"gix-fs", "gix-fs",
"gix-glob", "gix-glob",
"gix-hash", "gix-hash",
"gix-hashtable", "gix-hashtable",
"gix-ignore",
"gix-index",
"gix-lock", "gix-lock",
"gix-macros", "gix-macros",
"gix-object", "gix-object",
"gix-odb", "gix-odb",
"gix-pack", "gix-pack",
"gix-path", "gix-path",
"gix-pathspec",
"gix-ref", "gix-ref",
"gix-refspec", "gix-refspec",
"gix-revision", "gix-revision",
"gix-revwalk", "gix-revwalk",
"gix-sec", "gix-sec",
"gix-submodule",
"gix-tempfile", "gix-tempfile",
"gix-trace", "gix-trace",
"gix-traverse", "gix-traverse",
"gix-url", "gix-url",
"gix-utils", "gix-utils",
"gix-validate", "gix-validate",
"gix-worktree",
"once_cell", "once_cell",
"parking_lot", "parking_lot",
"smallvec", "smallvec",
@ -571,7 +588,33 @@ dependencies = [
"gix-date", "gix-date",
"itoa", "itoa",
"thiserror", "thiserror",
"winnow 0.5.28", "winnow",
]
[[package]]
name = "gix-attributes"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "214ee3792e504ee1ce206b36dcafa4f328ca313d1e2ac0b41433d68ef4e14260"
dependencies = [
"bstr",
"gix-glob",
"gix-path",
"gix-quote",
"gix-trace",
"kstring",
"smallvec",
"thiserror",
"unicode-bom",
]
[[package]]
name = "gix-bitmap"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b6cd0f246180034ddafac9b00a112f19178135b21eb031b3f79355891f7325"
dependencies = [
"thiserror",
] ]
[[package]] [[package]]
@ -583,6 +626,18 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "gix-command"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce1ffc7db3fb50b7dae6ecd937a3527cb725f444614df2ad8988d81806f13f09"
dependencies = [
"bstr",
"gix-path",
"gix-trace",
"shell-words",
]
[[package]] [[package]]
name = "gix-commitgraph" name = "gix-commitgraph"
version = "0.24.0" version = "0.24.0"
@ -615,7 +670,7 @@ dependencies = [
"smallvec", "smallvec",
"thiserror", "thiserror",
"unicode-bom", "unicode-bom",
"winnow 0.5.28", "winnow",
] ]
[[package]] [[package]]
@ -690,6 +745,27 @@ dependencies = [
"walkdir", "walkdir",
] ]
[[package]]
name = "gix-filter"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9240862840fb740d209422937195e129e4ed3da49af212383260134bea8f6c1a"
dependencies = [
"bstr",
"encoding_rs",
"gix-attributes",
"gix-command",
"gix-hash",
"gix-object",
"gix-packetline-blocking",
"gix-path",
"gix-quote",
"gix-trace",
"gix-utils",
"smallvec",
"thiserror",
]
[[package]] [[package]]
name = "gix-fs" name = "gix-fs"
version = "0.10.0" version = "0.10.0"
@ -733,6 +809,44 @@ dependencies = [
"parking_lot", "parking_lot",
] ]
[[package]]
name = "gix-ignore"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f7069aaca4a05784c4cb44e392f0eaf627c6e57e05d3100c0e2386a37a682f0"
dependencies = [
"bstr",
"gix-glob",
"gix-path",
"gix-trace",
"unicode-bom",
]
[[package]]
name = "gix-index"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7152181ba8f0a3addc5075dd612cea31fc3e252b29c8be8c45f4892bf87426"
dependencies = [
"bitflags 2.4.2",
"bstr",
"btoi",
"filetime",
"gix-bitmap",
"gix-features",
"gix-fs",
"gix-hash",
"gix-lock",
"gix-object",
"gix-traverse",
"itoa",
"libc",
"memmap2",
"rustix",
"smallvec",
"thiserror",
]
[[package]] [[package]]
name = "gix-lock" name = "gix-lock"
version = "13.0.0" version = "13.0.0"
@ -771,7 +885,7 @@ dependencies = [
"itoa", "itoa",
"smallvec", "smallvec",
"thiserror", "thiserror",
"winnow 0.5.28", "winnow",
] ]
[[package]] [[package]]
@ -814,6 +928,18 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "gix-packetline-blocking"
version = "0.17.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca8ef6dd3ea50e26f3bf572e90c034d033c804d340cd1eb386392f184a9ba2f7"
dependencies = [
"bstr",
"faster-hex",
"gix-trace",
"thiserror",
]
[[package]] [[package]]
name = "gix-path" name = "gix-path"
version = "0.10.4" version = "0.10.4"
@ -827,6 +953,21 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "gix-pathspec"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cbd49750edb26b0a691e5246fc635fa554d344da825cd20fa9ee0da9c1b761f"
dependencies = [
"bitflags 2.4.2",
"bstr",
"gix-attributes",
"gix-config-value",
"gix-glob",
"gix-path",
"thiserror",
]
[[package]] [[package]]
name = "gix-quote" name = "gix-quote"
version = "0.4.10" version = "0.4.10"
@ -857,7 +998,7 @@ dependencies = [
"gix-validate", "gix-validate",
"memmap2", "memmap2",
"thiserror", "thiserror",
"winnow 0.5.28", "winnow",
] ]
[[package]] [[package]]
@ -917,6 +1058,21 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "gix-submodule"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73182f6c1f5ed1ed94ba16581ac62593d5e29cd1c028b2af618f836283b8f8d4"
dependencies = [
"bstr",
"gix-config",
"gix-path",
"gix-pathspec",
"gix-refspec",
"gix-url",
"thiserror",
]
[[package]] [[package]]
name = "gix-tempfile" name = "gix-tempfile"
version = "13.0.0" version = "13.0.0"
@ -986,6 +1142,24 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "gix-worktree"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca36bb3dc54038c66507dc75c4d8edbee2d6d5cc45227b4eb508ad13dd60a006"
dependencies = [
"bstr",
"gix-attributes",
"gix-features",
"gix-fs",
"gix-glob",
"gix-hash",
"gix-ignore",
"gix-index",
"gix-object",
"gix-path",
]
[[package]] [[package]]
name = "globset" name = "globset"
version = "0.4.14" version = "0.4.14"
@ -1063,6 +1237,7 @@ dependencies = [
"dunce", "dunce",
"encoding_rs", "encoding_rs",
"etcetera", "etcetera",
"globset",
"hashbrown 0.14.3", "hashbrown 0.14.3",
"helix-loader", "helix-loader",
"helix-stdx", "helix-stdx",
@ -1141,6 +1316,7 @@ name = "helix-lsp"
version = "23.10.0" version = "23.10.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"arc-swap",
"futures-executor", "futures-executor",
"futures-util", "futures-util",
"globset", "globset",
@ -1209,6 +1385,7 @@ dependencies = [
"signal-hook-tokio", "signal-hook-tokio",
"smallvec", "smallvec",
"tempfile", "tempfile",
"termini",
"tokio", "tokio",
"tokio-stream", "tokio-stream",
"toml", "toml",
@ -1408,11 +1585,20 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "kstring"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec3066350882a1cd6d950d055997f379ac37fd39f81cd4d8ed186032eb3c5747"
dependencies = [
"static_assertions",
]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.152" version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]] [[package]]
name = "libloading" name = "libloading"
@ -1653,11 +1839,11 @@ checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79"
[[package]] [[package]]
name = "pulldown-cmark" name = "pulldown-cmark"
version = "0.9.3" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998" checksum = "dce76ce678ffc8e5675b22aa1405de0b7037e2fdf8913fea40d1926c6fe1e6e7"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags 2.4.2",
"memchr", "memchr",
"unicase", "unicase",
] ]
@ -1785,9 +1971,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.30" version = "0.38.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
dependencies = [ dependencies = [
"bitflags 2.4.2", "bitflags 2.4.2",
"errno", "errno",
@ -1825,18 +2011,18 @@ checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.195" version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.195" version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1845,9 +2031,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.111" version = "1.0.113"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
@ -1867,9 +2053,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_spanned" name = "serde_spanned"
version = "0.6.3" version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@ -1880,6 +2066,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
[[package]]
name = "shell-words"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
[[package]] [[package]]
name = "signal-hook" name = "signal-hook"
version = "0.3.17" version = "0.3.17"
@ -2009,13 +2201,12 @@ dependencies = [
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.9.0" version = "3.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand", "fastrand",
"redox_syscall 0.4.1",
"rustix", "rustix",
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
@ -2051,18 +2242,18 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.56" version = "1.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.56" version = "1.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2124,9 +2315,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.35.1" version = "1.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@ -2165,9 +2356,9 @@ dependencies = [
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.7.6" version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290"
dependencies = [ dependencies = [
"serde", "serde",
"serde_spanned", "serde_spanned",
@ -2177,24 +2368,24 @@ dependencies = [
[[package]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "0.6.3" version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
dependencies = [ dependencies = [
"serde", "serde",
] ]
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.19.12" version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78" checksum = "0c9ffdf896f8daaabf9b66ba8e77ea1ed5ed0f72821b398aba62352e95062951"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde", "serde",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",
"winnow 0.4.6", "winnow",
] ]
[[package]] [[package]]
@ -2256,9 +2447,9 @@ dependencies = [
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
version = "1.10.1" version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
@ -2605,15 +2796,6 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "winnow"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.5.28" version = "0.5.28"

@ -340,7 +340,12 @@ Currently unused
#### `[editor.gutters.diff]` Section #### `[editor.gutters.diff]` Section
Currently unused The `diff` gutter option displays colored bars indicating whether a `git` diff represents that a line was added, removed or changed.
These colors are controlled by the theme attributes `diff.plus`, `diff.minus` and `diff.delta`.
Other diff providers will eventually be supported by a future plugin system.
There are currently no options for this section.
#### `[editor.gutters.spacer]` Section #### `[editor.gutters.spacer]` Section

@ -14,6 +14,7 @@
| cabal | | | | `haskell-language-server-wrapper` | | cabal | | | | `haskell-language-server-wrapper` |
| cairo | ✓ | ✓ | ✓ | `cairo-language-server` | | cairo | ✓ | ✓ | ✓ | `cairo-language-server` |
| capnp | ✓ | | ✓ | | | capnp | ✓ | | ✓ | |
| cel | ✓ | | | |
| clojure | ✓ | | | `clojure-lsp` | | clojure | ✓ | | | `clojure-lsp` |
| cmake | ✓ | ✓ | ✓ | `cmake-language-server` | | cmake | ✓ | ✓ | ✓ | `cmake-language-server` |
| comment | ✓ | | | | | comment | ✓ | | | |
@ -69,6 +70,7 @@
| hcl | ✓ | | ✓ | `terraform-ls` | | hcl | ✓ | | ✓ | `terraform-ls` |
| heex | ✓ | ✓ | | `elixir-ls` | | heex | ✓ | ✓ | | `elixir-ls` |
| hocon | ✓ | | ✓ | | | hocon | ✓ | | ✓ | |
| hoon | ✓ | | | |
| hosts | ✓ | | | | | hosts | ✓ | | | |
| html | ✓ | | | `vscode-html-language-server` | | html | ✓ | | | `vscode-html-language-server` |
| hurl | ✓ | | ✓ | | | hurl | ✓ | | ✓ | |
@ -123,6 +125,7 @@
| pem | ✓ | | | | | pem | ✓ | | | |
| perl | ✓ | ✓ | ✓ | `perlnavigator` | | perl | ✓ | ✓ | ✓ | `perlnavigator` |
| php | ✓ | ✓ | ✓ | `intelephense` | | php | ✓ | ✓ | ✓ | `intelephense` |
| pkl | ✓ | | ✓ | |
| po | ✓ | ✓ | | | | po | ✓ | ✓ | | |
| pod | ✓ | | | | | pod | ✓ | | | |
| ponylang | ✓ | ✓ | ✓ | | | ponylang | ✓ | ✓ | ✓ | |
@ -148,11 +151,12 @@
| scala | ✓ | ✓ | ✓ | `metals` | | scala | ✓ | ✓ | ✓ | `metals` |
| scheme | ✓ | | ✓ | | | scheme | ✓ | | ✓ | |
| scss | ✓ | | | `vscode-css-language-server` | | scss | ✓ | | | `vscode-css-language-server` |
| slint | ✓ | | ✓ | `slint-lsp` | | slint | ✓ | | ✓ | `slint-lsp` |
| smali | ✓ | | ✓ | | | smali | ✓ | | ✓ | |
| smithy | ✓ | | | `cs` | | smithy | ✓ | | | `cs` |
| sml | ✓ | | | | | sml | ✓ | | | |
| solidity | ✓ | | | `solc` | | solidity | ✓ | | | `solc` |
| spicedb | ✓ | | | |
| sql | ✓ | | | | | sql | ✓ | | | |
| sshclientconfig | ✓ | | | | | sshclientconfig | ✓ | | | |
| starlark | ✓ | ✓ | | | | starlark | ✓ | ✓ | | |
@ -162,6 +166,7 @@
| swift | ✓ | | | `sourcekit-lsp` | | swift | ✓ | | | `sourcekit-lsp` |
| t32 | ✓ | | | | | t32 | ✓ | | | |
| tablegen | ✓ | ✓ | ✓ | | | tablegen | ✓ | ✓ | ✓ | |
| tact | ✓ | ✓ | ✓ | |
| task | ✓ | | | | | task | ✓ | | | |
| templ | ✓ | | | `templ` | | templ | ✓ | | | `templ` |
| tfvars | ✓ | | ✓ | `terraform-ls` | | tfvars | ✓ | | ✓ | `terraform-ls` |
@ -173,7 +178,7 @@
| typescript | ✓ | ✓ | ✓ | `typescript-language-server` | | typescript | ✓ | ✓ | ✓ | `typescript-language-server` |
| typst | ✓ | | | `typst-lsp` | | typst | ✓ | | | `typst-lsp` |
| ungrammar | ✓ | | | | | ungrammar | ✓ | | | |
| unison | ✓ | | | | | unison | ✓ | | | |
| uxntal | ✓ | | | | | uxntal | ✓ | | | |
| v | ✓ | ✓ | ✓ | `v-analyzer` | | v | ✓ | ✓ | ✓ | `v-analyzer` |
| vala | ✓ | | | `vala-language-server` | | vala | ✓ | | | `vala-language-server` |

@ -76,6 +76,15 @@ Releases are available in the `extra` repository:
```sh ```sh
sudo pacman -S helix sudo pacman -S helix
``` ```
> 💡 When installed from the `extra` repository, run Helix with `helix` instead of `hx`.
>
> For example:
> ```sh
> helix --health
> ```
> to check health
Additionally, a [helix-git](https://aur.archlinux.org/packages/helix-git/) package is available Additionally, a [helix-git](https://aur.archlinux.org/packages/helix-git/) package is available
in the AUR, which builds the master branch. in the AUR, which builds the master branch.

@ -78,24 +78,26 @@ from the above section. `file-types` is a list of strings or tables, for
example: example:
```toml ```toml
file-types = ["Makefile", "toml", { suffix = ".git/config" }] file-types = ["toml", { glob = "Makefile" }, { glob = ".git/config" }, { glob = ".github/workflows/*.yaml" } ]
``` ```
When determining a language configuration to use, Helix searches the file-types When determining a language configuration to use, Helix searches the file-types
with the following priorities: with the following priorities:
1. Exact match: if the filename of a file is an exact match of a string in a 1. Glob: values in `glob` tables are checked against the full path of the given
`file-types` list, that language wins. In the example above, `"Makefile"` file. Globs are standard Unix-style path globs (e.g. the kind you use in Shell)
will match against `Makefile` files. and can be used to match paths for a specific prefix, suffix, directory, etc.
2. Extension: if there are no exact matches, any `file-types` string that In the above example, the `{ glob = "Makefile" }` config would match files
matches the file extension of a given file wins. In the example above, the with the name `Makefile`, the `{ glob = ".git/config" }` config would match
`"toml"` matches files like `Cargo.toml` or `languages.toml`. `config` files in `.git` directories, and the `{ glob = ".github/workflows/*.yaml" }`
3. Suffix: if there are still no matches, any values in `suffix` tables config would match any `yaml` files in `.github/workflow` directories. Note
are checked against the full path of the given file. In the example above, that globs should always use the Unix path separator `/` even on Windows systems;
the `{ suffix = ".git/config" }` would match against any `config` files the matcher will automatically take the machine-specific separators into account.
in `.git` directories. Note: `/` is used as the directory separator but is If the glob isn't an absolute path or doesn't already start with a glob prefix,
replaced at runtime with the appropriate path separator for the operating `*/` will automatically be added to ensure it matches for any subdirectory.
system, so this rule would match against `.git\config` files on Windows. 2. Extension: if there are no glob matches, any `file-types` string that matches
the file extension of a given file wins. In the example above, the `"toml"`
config matches files like `Cargo.toml` or `languages.toml`.
## Language Server configuration ## Language Server configuration
@ -120,13 +122,14 @@ languages = { typescript = [ { formatCommand ="prettier --stdin-filepath ${INPUT
These are the available options for a language server. These are the available options for a language server.
| Key | Description | | Key | Description |
| ---- | ----------- | | ---- | ----------- |
| `command` | The name or path of the language server binary to execute. Binaries must be in `$PATH` | | `command` | The name or path of the language server binary to execute. Binaries must be in `$PATH` |
| `args` | A list of arguments to pass to the language server binary | | `args` | A list of arguments to pass to the language server binary |
| `config` | LSP initialization options | | `config` | LSP initialization options |
| `timeout` | The maximum time a request to the language server may take, in seconds. Defaults to `20` | | `timeout` | The maximum time a request to the language server may take, in seconds. Defaults to `20` |
| `environment` | Any environment variables that will be used when starting the language server `{ "KEY1" = "Value1", "KEY2" = "Value2" }` | | `environment` | Any environment variables that will be used when starting the language server `{ "KEY1" = "Value1", "KEY2" = "Value2" }` |
| `required-root-patterns` | A list of `glob` patterns to look for in the working directory. The language server is started if at least one of them is found. |
A `format` sub-table within `config` can be used to pass extra formatting options to A `format` sub-table within `config` can be used to pass extra formatting options to
[Document Formatting Requests](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_formatting). [Document Formatting Requests](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_formatting).

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

@ -22,7 +22,7 @@ helix-loader = { path = "../helix-loader" }
ropey = { version = "1.6.1", default-features = false, features = ["simd"] } ropey = { version = "1.6.1", default-features = false, features = ["simd"] }
smallvec = "1.13" smallvec = "1.13"
smartstring = "1.0.1" smartstring = "1.0.1"
unicode-segmentation = "1.10" unicode-segmentation = "1.11"
unicode-width = "0.1" unicode-width = "0.1"
unicode-general-category = "0.6" unicode-general-category = "0.6"
# slab = "0.4.2" # slab = "0.4.2"
@ -39,7 +39,7 @@ dunce = "1.0"
log = "0.4" log = "0.4"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
toml = "0.7" toml = "0.8"
imara-diff = "0.1.0" imara-diff = "0.1.0"
@ -52,6 +52,7 @@ textwrap = "0.16.0"
nucleo.workspace = true nucleo.workspace = true
parking_lot = "0.12" parking_lot = "0.12"
globset = "0.4.14"
[dev-dependencies] [dev-dependencies]
quickcheck = { version = "1", default-features = false } quickcheck = { version = "1", default-features = false }

@ -1,10 +1,45 @@
/// Syntax configuration loader based on built-in languages.toml. use crate::syntax::{Configuration, Loader, LoaderError};
pub fn default_syntax_loader() -> crate::syntax::Configuration {
/// Language configuration based on built-in languages.toml.
pub fn default_lang_config() -> Configuration {
helix_loader::config::default_lang_config() helix_loader::config::default_lang_config()
.try_into() .try_into()
.expect("Could not serialize built-in languages.toml") .expect("Could not deserialize built-in languages.toml")
} }
/// Syntax configuration loader based on user configured languages.toml.
pub fn user_syntax_loader() -> Result<crate::syntax::Configuration, toml::de::Error> { /// Language configuration loader based on built-in languages.toml.
pub fn default_lang_loader() -> Loader {
Loader::new(default_lang_config()).expect("Could not compile loader for default config")
}
#[derive(Debug)]
pub enum LanguageLoaderError {
DeserializeError(toml::de::Error),
LoaderError(LoaderError),
}
impl std::fmt::Display for LanguageLoaderError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::DeserializeError(err) => write!(f, "Failed to parse language config: {err}"),
Self::LoaderError(err) => write!(f, "Failed to compile language config: {err}"),
}
}
}
impl std::error::Error for LanguageLoaderError {}
/// Language configuration based on user configured languages.toml.
pub fn user_lang_config() -> Result<Configuration, toml::de::Error> {
helix_loader::config::user_lang_config()?.try_into() helix_loader::config::user_lang_config()?.try_into()
} }
/// Language configuration loader based on user configured languages.toml.
pub fn user_lang_loader() -> Result<Loader, LanguageLoaderError> {
let config: Configuration = helix_loader::config::user_lang_config()
.map_err(LanguageLoaderError::DeserializeError)?
.try_into()
.map_err(LanguageLoaderError::DeserializeError)?;
Loader::new(config).map_err(LanguageLoaderError::LoaderError)
}

@ -57,10 +57,10 @@ fn find_pair(
pos_: usize, pos_: usize,
traverse_parents: bool, traverse_parents: bool,
) -> Option<usize> { ) -> Option<usize> {
let tree = syntax.tree();
let pos = doc.char_to_byte(pos_); let pos = doc.char_to_byte(pos_);
let mut node = tree.root_node().descendant_for_byte_range(pos, pos + 1)?; let root = syntax.tree_for_byte_range(pos, pos + 1).root_node();
let mut node = root.descendant_for_byte_range(pos, pos + 1)?;
loop { loop {
if node.is_named() { if node.is_named() {
@ -118,9 +118,7 @@ fn find_pair(
}; };
node = parent; node = parent;
} }
let node = tree let node = root.named_descendant_for_byte_range(pos, pos + 1)?;
.root_node()
.named_descendant_for_byte_range(pos, pos + 1)?;
if node.child_count() != 0 { if node.child_count() != 0 {
return None; return None;
} }

@ -573,16 +573,11 @@ pub fn move_parent_node_end(
dir: Direction, dir: Direction,
movement: Movement, movement: Movement,
) -> Selection { ) -> Selection {
let tree = syntax.tree();
selection.transform(|range| { selection.transform(|range| {
let start_from = text.char_to_byte(range.from()); let start_from = text.char_to_byte(range.from());
let start_to = text.char_to_byte(range.to()); let start_to = text.char_to_byte(range.to());
let mut node = match tree let mut node = match syntax.named_descendant_for_byte_range(start_from, start_to) {
.root_node()
.named_descendant_for_byte_range(start_from, start_to)
{
Some(node) => node, Some(node) => node,
None => { None => {
log::debug!( log::debug!(

@ -10,6 +10,7 @@ use crate::{
use ahash::RandomState; use ahash::RandomState;
use arc_swap::{ArcSwap, Guard}; use arc_swap::{ArcSwap, Guard};
use bitflags::bitflags; use bitflags::bitflags;
use globset::GlobSet;
use hashbrown::raw::RawTable; use hashbrown::raw::RawTable;
use slotmap::{DefaultKey as LayerId, HopSlotMap}; use slotmap::{DefaultKey as LayerId, HopSlotMap};
@ -82,12 +83,6 @@ pub struct Configuration {
pub language_server: HashMap<String, LanguageServerConfiguration>, pub language_server: HashMap<String, LanguageServerConfiguration>,
} }
impl Default for Configuration {
fn default() -> Self {
crate::config::default_syntax_loader()
}
}
// largely based on tree-sitter/cli/src/loader.rs // largely based on tree-sitter/cli/src/loader.rs
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)] #[serde(rename_all = "kebab-case", deny_unknown_fields)]
@ -164,9 +159,11 @@ pub enum FileType {
/// The extension of the file, either the `Path::extension` or the full /// The extension of the file, either the `Path::extension` or the full
/// filename if the file does not have an extension. /// filename if the file does not have an extension.
Extension(String), Extension(String),
/// The suffix of a file. This is compared to a given file's absolute /// A Unix-style path glob. This is compared to the file's absolute path, so
/// path, so it can be used to detect files based on their directories. /// it can be used to detect files based on their directories. If the glob
Suffix(String), /// is not an absolute path and does not already start with a glob pattern,
/// a glob pattern will be prepended to it.
Glob(globset::Glob),
} }
impl Serialize for FileType { impl Serialize for FileType {
@ -178,9 +175,9 @@ impl Serialize for FileType {
match self { match self {
FileType::Extension(extension) => serializer.serialize_str(extension), FileType::Extension(extension) => serializer.serialize_str(extension),
FileType::Suffix(suffix) => { FileType::Glob(glob) => {
let mut map = serializer.serialize_map(Some(1))?; let mut map = serializer.serialize_map(Some(1))?;
map.serialize_entry("suffix", &suffix.replace(std::path::MAIN_SEPARATOR, "/"))?; map.serialize_entry("glob", glob.glob())?;
map.end() map.end()
} }
} }
@ -213,9 +210,20 @@ impl<'de> Deserialize<'de> for FileType {
M: serde::de::MapAccess<'de>, M: serde::de::MapAccess<'de>,
{ {
match map.next_entry::<String, String>()? { match map.next_entry::<String, String>()? {
Some((key, suffix)) if key == "suffix" => Ok(FileType::Suffix({ Some((key, mut glob)) if key == "glob" => {
suffix.replace('/', std::path::MAIN_SEPARATOR_STR) // If the glob isn't an absolute path or already starts
})), // with a glob pattern, add a leading glob so we
// properly match relative paths.
if !glob.starts_with('/') && !glob.starts_with("*/") {
glob.insert_str(0, "*/");
}
globset::Glob::new(glob.as_str())
.map(FileType::Glob)
.map_err(|err| {
serde::de::Error::custom(format!("invalid `glob` pattern: {}", err))
})
}
Some((key, _value)) => Err(serde::de::Error::custom(format!( Some((key, _value)) => Err(serde::de::Error::custom(format!(
"unknown key in `file-types` list: {}", "unknown key in `file-types` list: {}",
key key
@ -358,6 +366,22 @@ where
serializer.end() serializer.end()
} }
fn deserialize_required_root_patterns<'de, D>(deserializer: D) -> Result<Option<GlobSet>, D::Error>
where
D: serde::Deserializer<'de>,
{
let patterns = Vec::<String>::deserialize(deserializer)?;
if patterns.is_empty() {
return Ok(None);
}
let mut builder = globset::GlobSetBuilder::new();
for pattern in patterns {
let glob = globset::Glob::new(&pattern).map_err(serde::de::Error::custom)?;
builder.add(glob);
}
builder.build().map(Some).map_err(serde::de::Error::custom)
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct LanguageServerConfiguration { pub struct LanguageServerConfiguration {
@ -371,6 +395,12 @@ pub struct LanguageServerConfiguration {
pub config: Option<serde_json::Value>, pub config: Option<serde_json::Value>,
#[serde(default = "default_timeout")] #[serde(default = "default_timeout")]
pub timeout: u64, pub timeout: u64,
#[serde(
default,
skip_serializing,
deserialize_with = "deserialize_required_root_patterns"
)]
pub required_root_patterns: Option<GlobSet>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@ -752,6 +782,47 @@ pub struct SoftWrap {
pub wrap_at_text_width: Option<bool>, pub wrap_at_text_width: Option<bool>,
} }
#[derive(Debug)]
struct FileTypeGlob {
glob: globset::Glob,
language_id: usize,
}
impl FileTypeGlob {
fn new(glob: globset::Glob, language_id: usize) -> Self {
Self { glob, language_id }
}
}
#[derive(Debug)]
struct FileTypeGlobMatcher {
matcher: globset::GlobSet,
file_types: Vec<FileTypeGlob>,
}
impl FileTypeGlobMatcher {
fn new(file_types: Vec<FileTypeGlob>) -> Result<Self, globset::Error> {
let mut builder = globset::GlobSetBuilder::new();
for file_type in &file_types {
builder.add(file_type.glob.clone());
}
Ok(Self {
matcher: builder.build()?,
file_types,
})
}
fn language_id_for_path(&self, path: &Path) -> Option<&usize> {
self.matcher
.matches(path)
.iter()
.filter_map(|idx| self.file_types.get(*idx))
.max_by_key(|file_type| file_type.glob.glob().len())
.map(|file_type| &file_type.language_id)
}
}
// Expose loader as Lazy<> global since it's always static? // Expose loader as Lazy<> global since it's always static?
#[derive(Debug)] #[derive(Debug)]
@ -759,7 +830,7 @@ pub struct Loader {
// highlight_names ? // highlight_names ?
language_configs: Vec<Arc<LanguageConfiguration>>, language_configs: Vec<Arc<LanguageConfiguration>>,
language_config_ids_by_extension: HashMap<String, usize>, // Vec<usize> language_config_ids_by_extension: HashMap<String, usize>, // Vec<usize>
language_config_ids_by_suffix: HashMap<String, usize>, language_config_ids_glob_matcher: FileTypeGlobMatcher,
language_config_ids_by_shebang: HashMap<String, usize>, language_config_ids_by_shebang: HashMap<String, usize>,
language_server_configs: HashMap<String, LanguageServerConfiguration>, language_server_configs: HashMap<String, LanguageServerConfiguration>,
@ -767,66 +838,57 @@ pub struct Loader {
scopes: ArcSwap<Vec<String>>, scopes: ArcSwap<Vec<String>>,
} }
pub type LoaderError = globset::Error;
impl Loader { impl Loader {
pub fn new(config: Configuration) -> Self { pub fn new(config: Configuration) -> Result<Self, LoaderError> {
let mut loader = Self { let mut language_configs = Vec::new();
language_configs: Vec::new(), let mut language_config_ids_by_extension = HashMap::new();
language_server_configs: config.language_server, let mut language_config_ids_by_shebang = HashMap::new();
language_config_ids_by_extension: HashMap::new(), let mut file_type_globs = Vec::new();
language_config_ids_by_suffix: HashMap::new(),
language_config_ids_by_shebang: HashMap::new(),
scopes: ArcSwap::from_pointee(Vec::new()),
};
for config in config.language { for config in config.language {
// get the next id // get the next id
let language_id = loader.language_configs.len(); let language_id = language_configs.len();
for file_type in &config.file_types { for file_type in &config.file_types {
// entry().or_insert(Vec::new).push(language_id); // entry().or_insert(Vec::new).push(language_id);
match file_type { match file_type {
FileType::Extension(extension) => loader FileType::Extension(extension) => {
.language_config_ids_by_extension language_config_ids_by_extension.insert(extension.clone(), language_id);
.insert(extension.clone(), language_id), }
FileType::Suffix(suffix) => loader FileType::Glob(glob) => {
.language_config_ids_by_suffix file_type_globs.push(FileTypeGlob::new(glob.to_owned(), language_id));
.insert(suffix.clone(), language_id), }
}; };
} }
for shebang in &config.shebangs { for shebang in &config.shebangs {
loader language_config_ids_by_shebang.insert(shebang.clone(), language_id);
.language_config_ids_by_shebang
.insert(shebang.clone(), language_id);
} }
loader.language_configs.push(Arc::new(config)); language_configs.push(Arc::new(config));
} }
loader Ok(Self {
language_configs,
language_config_ids_by_extension,
language_config_ids_glob_matcher: FileTypeGlobMatcher::new(file_type_globs)?,
language_config_ids_by_shebang,
language_server_configs: config.language_server,
scopes: ArcSwap::from_pointee(Vec::new()),
})
} }
pub fn language_config_for_file_name(&self, path: &Path) -> Option<Arc<LanguageConfiguration>> { pub fn language_config_for_file_name(&self, path: &Path) -> Option<Arc<LanguageConfiguration>> {
// Find all the language configurations that match this file name // Find all the language configurations that match this file name
// or a suffix of the file name. // or a suffix of the file name.
let configuration_id = path let configuration_id = self
.file_name() .language_config_ids_glob_matcher
.and_then(|n| n.to_str()) .language_id_for_path(path)
.and_then(|file_name| self.language_config_ids_by_extension.get(file_name))
.or_else(|| { .or_else(|| {
path.extension() path.extension()
.and_then(|extension| extension.to_str()) .and_then(|extension| extension.to_str())
.and_then(|extension| self.language_config_ids_by_extension.get(extension)) .and_then(|extension| self.language_config_ids_by_extension.get(extension))
})
.or_else(|| {
self.language_config_ids_by_suffix
.iter()
.find_map(|(file_type, id)| {
if path.to_str()?.ends_with(file_type) {
Some(id)
} else {
None
}
})
}); });
configuration_id.and_then(|&id| self.language_configs.get(id).cloned()) configuration_id.and_then(|&id| self.language_configs.get(id).cloned())
@ -938,7 +1000,7 @@ thread_local! {
pub struct Syntax { pub struct Syntax {
layers: HopSlotMap<LayerId, LanguageLayer>, layers: HopSlotMap<LayerId, LanguageLayer>,
root: LayerId, root: LayerId,
loader: Arc<Loader>, loader: Arc<ArcSwap<Loader>>,
} }
fn byte_range_to_str(range: std::ops::Range<usize>, source: RopeSlice) -> Cow<str> { fn byte_range_to_str(range: std::ops::Range<usize>, source: RopeSlice) -> Cow<str> {
@ -949,7 +1011,7 @@ impl Syntax {
pub fn new( pub fn new(
source: RopeSlice, source: RopeSlice,
config: Arc<HighlightConfiguration>, config: Arc<HighlightConfiguration>,
loader: Arc<Loader>, loader: Arc<ArcSwap<Loader>>,
) -> Option<Self> { ) -> Option<Self> {
let root_layer = LanguageLayer { let root_layer = LanguageLayer {
tree: None, tree: None,
@ -993,9 +1055,10 @@ impl Syntax {
let mut queue = VecDeque::new(); let mut queue = VecDeque::new();
queue.push_back(self.root); queue.push_back(self.root);
let scopes = self.loader.scopes.load(); let loader = self.loader.load();
let scopes = loader.scopes.load();
let injection_callback = |language: &InjectionLanguageMarker| { let injection_callback = |language: &InjectionLanguageMarker| {
self.loader loader
.language_configuration_for_injection_string(language) .language_configuration_for_injection_string(language)
.and_then(|language_config| language_config.highlight_config(&scopes)) .and_then(|language_config| language_config.highlight_config(&scopes))
}; };
@ -1338,7 +1401,7 @@ impl Syntax {
result result
} }
pub fn descendant_for_byte_range(&self, start: usize, end: usize) -> Option<Node<'_>> { pub fn tree_for_byte_range(&self, start: usize, end: usize) -> &Tree {
let mut container_id = self.root; let mut container_id = self.root;
for (layer_id, layer) in self.layers.iter() { for (layer_id, layer) in self.layers.iter() {
@ -1349,8 +1412,17 @@ impl Syntax {
} }
} }
self.layers[container_id] self.layers[container_id].tree()
.tree() }
pub fn named_descendant_for_byte_range(&self, start: usize, end: usize) -> Option<Node<'_>> {
self.tree_for_byte_range(start, end)
.root_node()
.named_descendant_for_byte_range(start, end)
}
pub fn descendant_for_byte_range(&self, start: usize, end: usize) -> Option<Node<'_>> {
self.tree_for_byte_range(start, end)
.root_node() .root_node()
.descendant_for_byte_range(start, end) .descendant_for_byte_range(start, end)
} }
@ -2583,7 +2655,8 @@ mod test {
let loader = Loader::new(Configuration { let loader = Loader::new(Configuration {
language: vec![], language: vec![],
language_server: HashMap::new(), language_server: HashMap::new(),
}); })
.unwrap();
let language = get_language("rust").unwrap(); let language = get_language("rust").unwrap();
let query = Query::new(language, query_str).unwrap(); let query = Query::new(language, query_str).unwrap();
@ -2591,7 +2664,12 @@ mod test {
let mut cursor = QueryCursor::new(); let mut cursor = QueryCursor::new();
let config = HighlightConfiguration::new(language, "", "", "").unwrap(); let config = HighlightConfiguration::new(language, "", "", "").unwrap();
let syntax = Syntax::new(source.slice(..), Arc::new(config), Arc::new(loader)).unwrap(); let syntax = Syntax::new(
source.slice(..),
Arc::new(config),
Arc::new(ArcSwap::from_pointee(loader)),
)
.unwrap();
let root = syntax.tree().root_node(); let root = syntax.tree().root_node();
let mut test = |capture, range| { let mut test = |capture, range| {
@ -2645,7 +2723,8 @@ mod test {
let loader = Loader::new(Configuration { let loader = Loader::new(Configuration {
language: vec![], language: vec![],
language_server: HashMap::new(), language_server: HashMap::new(),
}); })
.unwrap();
let language = get_language("rust").unwrap(); let language = get_language("rust").unwrap();
let config = HighlightConfiguration::new( let config = HighlightConfiguration::new(
@ -2665,7 +2744,12 @@ mod test {
fn main() {} fn main() {}
", ",
); );
let syntax = Syntax::new(source.slice(..), Arc::new(config), Arc::new(loader)).unwrap(); let syntax = Syntax::new(
source.slice(..),
Arc::new(config),
Arc::new(ArcSwap::from_pointee(loader)),
)
.unwrap();
let tree = syntax.tree(); let tree = syntax.tree();
let root = tree.root_node(); let root = tree.root_node();
assert_eq!(root.kind(), "source_file"); assert_eq!(root.kind(), "source_file");
@ -2751,11 +2835,17 @@ mod test {
let loader = Loader::new(Configuration { let loader = Loader::new(Configuration {
language: vec![], language: vec![],
language_server: HashMap::new(), language_server: HashMap::new(),
}); })
.unwrap();
let language = get_language(language_name).unwrap(); let language = get_language(language_name).unwrap();
let config = HighlightConfiguration::new(language, "", "", "").unwrap(); let config = HighlightConfiguration::new(language, "", "", "").unwrap();
let syntax = Syntax::new(source.slice(..), Arc::new(config), Arc::new(loader)).unwrap(); let syntax = Syntax::new(
source.slice(..),
Arc::new(config),
Arc::new(ArcSwap::from_pointee(loader)),
)
.unwrap();
let root = syntax let root = syntax
.tree() .tree()

@ -1,10 +1,11 @@
use arc_swap::ArcSwap;
use helix_core::{ use helix_core::{
indent::{indent_level_for_line, treesitter_indent_for_pos, IndentStyle}, indent::{indent_level_for_line, treesitter_indent_for_pos, IndentStyle},
syntax::{Configuration, Loader}, syntax::{Configuration, Loader},
Syntax, Syntax,
}; };
use ropey::Rope; use ropey::Rope;
use std::{ops::Range, path::PathBuf, process::Command}; use std::{ops::Range, path::PathBuf, process::Command, sync::Arc};
#[test] #[test]
fn test_treesitter_indent_rust() { fn test_treesitter_indent_rust() {
@ -186,7 +187,7 @@ fn test_treesitter_indent(
lang_scope: &str, lang_scope: &str,
ignored_lines: Vec<std::ops::Range<usize>>, ignored_lines: Vec<std::ops::Range<usize>>,
) { ) {
let loader = Loader::new(indent_tests_config()); let loader = Loader::new(indent_tests_config()).unwrap();
// set runtime path so we can find the queries // set runtime path so we can find the queries
let mut runtime = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); let mut runtime = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
@ -197,7 +198,12 @@ fn test_treesitter_indent(
let indent_style = IndentStyle::from_str(&language_config.indent.as_ref().unwrap().unit); let indent_style = IndentStyle::from_str(&language_config.indent.as_ref().unwrap().unit);
let highlight_config = language_config.highlight_config(&[]).unwrap(); let highlight_config = language_config.highlight_config(&[]).unwrap();
let text = doc.slice(..); let text = doc.slice(..);
let syntax = Syntax::new(text, highlight_config, std::sync::Arc::new(loader)).unwrap(); let syntax = Syntax::new(
text,
highlight_config,
Arc::new(ArcSwap::from_pointee(loader)),
)
.unwrap();
let indent_query = language_config.indent_query().unwrap(); let indent_query = language_config.indent_query().unwrap();
for i in 0..doc.len_lines() { for i in 0..doc.len_lines() {

@ -19,7 +19,7 @@ helix-stdx = { path = "../helix-stdx" }
anyhow = "1" anyhow = "1"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
toml = "0.7" toml = "0.8"
etcetera = "0.8" etcetera = "0.8"
tree-sitter.workspace = true tree-sitter.workspace = true
once_cell = "1.19" once_cell = "1.19"
@ -30,7 +30,7 @@ log = "0.4"
# cloning/compiling tree-sitter grammars # cloning/compiling tree-sitter grammars
cc = { version = "1" } cc = { version = "1" }
threadpool = { version = "1.0" } threadpool = { version = "1.0" }
tempfile = "3.9.0" tempfile = "3.10.0"
dunce = "1.0.4" dunce = "1.0.4"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]

@ -27,6 +27,7 @@ lsp-types = { version = "0.95" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
thiserror = "1.0" thiserror = "1.0"
tokio = { version = "1.35", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] } tokio = { version = "1.36", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
tokio-stream = "0.1.14" tokio-stream = "0.1.14"
parking_lot = "0.12.1" parking_lot = "0.12.1"
arc-swap = "1"

@ -1,4 +1,5 @@
use crate::{ use crate::{
file_operations::FileOperationsInterest,
find_lsp_workspace, jsonrpc, find_lsp_workspace, jsonrpc,
transport::{Payload, Transport}, transport::{Payload, Transport},
Call, Error, OffsetEncoding, Result, Call, Error, OffsetEncoding, Result,
@ -9,20 +10,20 @@ use helix_loader::{self, VERSION_AND_GIT_HASH};
use helix_stdx::path; use helix_stdx::path;
use lsp::{ use lsp::{
notification::DidChangeWorkspaceFolders, CodeActionCapabilityResolveSupport, notification::DidChangeWorkspaceFolders, CodeActionCapabilityResolveSupport,
DidChangeWorkspaceFoldersParams, OneOf, PositionEncodingKind, SignatureHelp, WorkspaceFolder, DidChangeWorkspaceFoldersParams, OneOf, PositionEncodingKind, SignatureHelp, Url,
WorkspaceFoldersChangeEvent, WorkspaceFolder, WorkspaceFoldersChangeEvent,
}; };
use lsp_types as lsp; use lsp_types as lsp;
use parking_lot::Mutex; use parking_lot::Mutex;
use serde::Deserialize; use serde::Deserialize;
use serde_json::Value; use serde_json::Value;
use std::future::Future;
use std::process::Stdio;
use std::sync::{ use std::sync::{
atomic::{AtomicU64, Ordering}, atomic::{AtomicU64, Ordering},
Arc, Arc,
}; };
use std::{collections::HashMap, path::PathBuf}; use std::{collections::HashMap, path::PathBuf};
use std::{future::Future, sync::OnceLock};
use std::{path::Path, process::Stdio};
use tokio::{ use tokio::{
io::{BufReader, BufWriter}, io::{BufReader, BufWriter},
process::{Child, Command}, process::{Child, Command},
@ -51,6 +52,7 @@ pub struct Client {
server_tx: UnboundedSender<Payload>, server_tx: UnboundedSender<Payload>,
request_counter: AtomicU64, request_counter: AtomicU64,
pub(crate) capabilities: OnceCell<lsp::ServerCapabilities>, pub(crate) capabilities: OnceCell<lsp::ServerCapabilities>,
pub(crate) file_operation_interest: OnceLock<FileOperationsInterest>,
config: Option<Value>, config: Option<Value>,
root_path: std::path::PathBuf, root_path: std::path::PathBuf,
root_uri: Option<lsp::Url>, root_uri: Option<lsp::Url>,
@ -175,12 +177,11 @@ impl Client {
args: &[String], args: &[String],
config: Option<Value>, config: Option<Value>,
server_environment: HashMap<String, String>, server_environment: HashMap<String, String>,
root_markers: &[String], root_path: PathBuf,
manual_roots: &[PathBuf], root_uri: Option<lsp::Url>,
id: usize, id: usize,
name: String, name: String,
req_timeout: u64, req_timeout: u64,
doc_path: Option<&std::path::PathBuf>,
) -> Result<(Self, UnboundedReceiver<(usize, Call)>, Arc<Notify>)> { ) -> Result<(Self, UnboundedReceiver<(usize, Call)>, Arc<Notify>)> {
// Resolve path to the binary // Resolve path to the binary
let cmd = helix_stdx::env::which(cmd)?; let cmd = helix_stdx::env::which(cmd)?;
@ -204,22 +205,6 @@ impl Client {
let (server_rx, server_tx, initialize_notify) = let (server_rx, server_tx, initialize_notify) =
Transport::start(reader, writer, stderr, id, name.clone()); Transport::start(reader, writer, stderr, id, name.clone());
let (workspace, workspace_is_cwd) = find_workspace();
let workspace = path::normalize(workspace);
let root = find_lsp_workspace(
doc_path
.and_then(|x| x.parent().and_then(|x| x.to_str()))
.unwrap_or("."),
root_markers,
manual_roots,
&workspace,
workspace_is_cwd,
);
// `root_uri` and `workspace_folder` can be empty in case there is no workspace
// `root_url` can not, use `workspace` as a fallback
let root_path = root.clone().unwrap_or_else(|| workspace.clone());
let root_uri = root.and_then(|root| lsp::Url::from_file_path(root).ok());
let workspace_folders = root_uri let workspace_folders = root_uri
.clone() .clone()
@ -233,6 +218,7 @@ impl Client {
server_tx, server_tx,
request_counter: AtomicU64::new(0), request_counter: AtomicU64::new(0),
capabilities: OnceCell::new(), capabilities: OnceCell::new(),
file_operation_interest: OnceLock::new(),
config, config,
req_timeout, req_timeout,
root_path, root_path,
@ -278,6 +264,11 @@ impl Client {
.expect("language server not yet initialized!") .expect("language server not yet initialized!")
} }
pub(crate) fn file_operations_intests(&self) -> &FileOperationsInterest {
self.file_operation_interest
.get_or_init(|| FileOperationsInterest::new(self.capabilities()))
}
/// Client has to be initialized otherwise this function panics /// Client has to be initialized otherwise this function panics
#[inline] #[inline]
pub fn supports_feature(&self, feature: LanguageServerFeature) -> bool { pub fn supports_feature(&self, feature: LanguageServerFeature) -> bool {
@ -717,27 +708,27 @@ impl Client {
}) })
} }
pub fn prepare_file_rename( pub fn will_rename(
&self, &self,
old_uri: &lsp::Url, old_path: &Path,
new_uri: &lsp::Url, new_path: &Path,
is_dir: bool,
) -> Option<impl Future<Output = Result<lsp::WorkspaceEdit>>> { ) -> Option<impl Future<Output = Result<lsp::WorkspaceEdit>>> {
let capabilities = self.capabilities.get().unwrap(); let capabilities = self.file_operations_intests();
if !capabilities.will_rename.has_interest(old_path, is_dir) {
// Return early if the server does not support willRename feature return None;
match &capabilities.workspace {
Some(workspace) => match &workspace.file_operations {
Some(op) => {
op.will_rename.as_ref()?;
}
_ => return None,
},
_ => return None,
} }
let url_from_path = |path| {
let url = if is_dir {
Url::from_directory_path(path)
} else {
Url::from_file_path(path)
};
Some(url.ok()?.to_string())
};
let files = vec![lsp::FileRename { let files = vec![lsp::FileRename {
old_uri: old_uri.to_string(), old_uri: url_from_path(old_path)?,
new_uri: new_uri.to_string(), new_uri: url_from_path(new_path)?,
}]; }];
let request = self.call_with_timeout::<lsp::request::WillRenameFiles>( let request = self.call_with_timeout::<lsp::request::WillRenameFiles>(
lsp::RenameFilesParams { files }, lsp::RenameFilesParams { files },
@ -751,27 +742,28 @@ impl Client {
}) })
} }
pub fn did_file_rename( pub fn did_rename(
&self, &self,
old_uri: &lsp::Url, old_path: &Path,
new_uri: &lsp::Url, new_path: &Path,
is_dir: bool,
) -> Option<impl Future<Output = std::result::Result<(), Error>>> { ) -> Option<impl Future<Output = std::result::Result<(), Error>>> {
let capabilities = self.capabilities.get().unwrap(); let capabilities = self.file_operations_intests();
if !capabilities.did_rename.has_interest(new_path, is_dir) {
// Return early if the server does not support DidRename feature return None;
match &capabilities.workspace {
Some(workspace) => match &workspace.file_operations {
Some(op) => {
op.did_rename.as_ref()?;
}
_ => return None,
},
_ => return None,
} }
let url_from_path = |path| {
let url = if is_dir {
Url::from_directory_path(path)
} else {
Url::from_file_path(path)
};
Some(url.ok()?.to_string())
};
let files = vec![lsp::FileRename { let files = vec![lsp::FileRename {
old_uri: old_uri.to_string(), old_uri: url_from_path(old_path)?,
new_uri: new_uri.to_string(), new_uri: url_from_path(new_path)?,
}]; }];
Some(self.notify::<lsp::notification::DidRenameFiles>(lsp::RenameFilesParams { files })) Some(self.notify::<lsp::notification::DidRenameFiles>(lsp::RenameFilesParams { files }))
} }

@ -0,0 +1,105 @@
use std::path::Path;
use globset::{GlobBuilder, GlobSet};
use crate::lsp;
#[derive(Default, Debug)]
pub(crate) struct FileOperationFilter {
dir_globs: GlobSet,
file_globs: GlobSet,
}
impl FileOperationFilter {
fn new(capability: Option<&lsp::FileOperationRegistrationOptions>) -> FileOperationFilter {
let Some(cap) = capability else {
return FileOperationFilter::default();
};
let mut dir_globs = GlobSet::builder();
let mut file_globs = GlobSet::builder();
for filter in &cap.filters {
// TODO: support other url schemes
let is_non_file_schema = filter
.scheme
.as_ref()
.is_some_and(|schema| schema != "file");
if is_non_file_schema {
continue;
}
let ignore_case = filter
.pattern
.options
.as_ref()
.and_then(|opts| opts.ignore_case)
.unwrap_or(false);
let mut glob_builder = GlobBuilder::new(&filter.pattern.glob);
glob_builder.case_insensitive(!ignore_case);
let glob = match glob_builder.build() {
Ok(glob) => glob,
Err(err) => {
log::error!("invalid glob send by LS: {err}");
continue;
}
};
match filter.pattern.matches {
Some(lsp::FileOperationPatternKind::File) => {
file_globs.add(glob);
}
Some(lsp::FileOperationPatternKind::Folder) => {
dir_globs.add(glob);
}
None => {
file_globs.add(glob.clone());
dir_globs.add(glob);
}
};
}
let file_globs = file_globs.build().unwrap_or_else(|err| {
log::error!("invalid globs send by LS: {err}");
GlobSet::empty()
});
let dir_globs = dir_globs.build().unwrap_or_else(|err| {
log::error!("invalid globs send by LS: {err}");
GlobSet::empty()
});
FileOperationFilter {
dir_globs,
file_globs,
}
}
pub(crate) fn has_interest(&self, path: &Path, is_dir: bool) -> bool {
if is_dir {
self.dir_globs.is_match(path)
} else {
self.file_globs.is_match(path)
}
}
}
#[derive(Default, Debug)]
pub(crate) struct FileOperationsInterest {
// TODO: support other notifications
// did_create: FileOperationFilter,
// will_create: FileOperationFilter,
pub did_rename: FileOperationFilter,
pub will_rename: FileOperationFilter,
// did_delete: FileOperationFilter,
// will_delete: FileOperationFilter,
}
impl FileOperationsInterest {
pub fn new(capabilities: &lsp::ServerCapabilities) -> FileOperationsInterest {
let capabilities = capabilities
.workspace
.as_ref()
.and_then(|capabilities| capabilities.file_operations.as_ref());
let Some(capabilities) = capabilities else {
return FileOperationsInterest::default();
};
FileOperationsInterest {
did_rename: FileOperationFilter::new(capabilities.did_rename.as_ref()),
will_rename: FileOperationFilter::new(capabilities.will_rename.as_ref()),
}
}
}

@ -1,9 +1,11 @@
mod client; mod client;
pub mod file_event; pub mod file_event;
mod file_operations;
pub mod jsonrpc; pub mod jsonrpc;
pub mod snippet; pub mod snippet;
mod transport; mod transport;
use arc_swap::ArcSwap;
pub use client::Client; pub use client::Client;
pub use futures_executor::block_on; pub use futures_executor::block_on;
pub use jsonrpc::Call; pub use jsonrpc::Call;
@ -639,14 +641,14 @@ impl Notification {
#[derive(Debug)] #[derive(Debug)]
pub struct Registry { pub struct Registry {
inner: HashMap<LanguageServerName, Vec<Arc<Client>>>, inner: HashMap<LanguageServerName, Vec<Arc<Client>>>,
syn_loader: Arc<helix_core::syntax::Loader>, syn_loader: Arc<ArcSwap<helix_core::syntax::Loader>>,
counter: usize, counter: usize,
pub incoming: SelectAll<UnboundedReceiverStream<(usize, Call)>>, pub incoming: SelectAll<UnboundedReceiverStream<(usize, Call)>>,
pub file_event_handler: file_event::Handler, pub file_event_handler: file_event::Handler,
} }
impl Registry { impl Registry {
pub fn new(syn_loader: Arc<helix_core::syntax::Loader>) -> Self { pub fn new(syn_loader: Arc<ArcSwap<helix_core::syntax::Loader>>) -> Self {
Self { Self {
inner: HashMap::new(), inner: HashMap::new(),
syn_loader, syn_loader,
@ -679,15 +681,15 @@ impl Registry {
doc_path: Option<&std::path::PathBuf>, doc_path: Option<&std::path::PathBuf>,
root_dirs: &[PathBuf], root_dirs: &[PathBuf],
enable_snippets: bool, enable_snippets: bool,
) -> Result<Arc<Client>> { ) -> Result<Option<Arc<Client>>> {
let config = self let syn_loader = self.syn_loader.load();
.syn_loader let config = syn_loader
.language_server_configs() .language_server_configs()
.get(&name) .get(&name)
.ok_or_else(|| anyhow::anyhow!("Language server '{name}' not defined"))?; .ok_or_else(|| anyhow::anyhow!("Language server '{name}' not defined"))?;
let id = self.counter; let id = self.counter;
self.counter += 1; self.counter += 1;
let NewClient(client, incoming) = start_client( if let Some(NewClient(client, incoming)) = start_client(
id, id,
name, name,
ls_config, ls_config,
@ -695,9 +697,12 @@ impl Registry {
doc_path, doc_path,
root_dirs, root_dirs,
enable_snippets, enable_snippets,
)?; )? {
self.incoming.push(UnboundedReceiverStream::new(incoming)); self.incoming.push(UnboundedReceiverStream::new(incoming));
Ok(client) Ok(Some(client))
} else {
Ok(None)
}
} }
/// If this method is called, all documents that have a reference to language servers used by the language config have to refresh their language servers, /// If this method is called, all documents that have a reference to language servers used by the language config have to refresh their language servers,
@ -722,8 +727,8 @@ impl Registry {
root_dirs, root_dirs,
enable_snippets, enable_snippets,
) { ) {
Ok(client) => client, Ok(client) => client?,
error => return Some(error), Err(error) => return Some(Err(error)),
}; };
let old_clients = self let old_clients = self
.inner .inner
@ -763,13 +768,13 @@ impl Registry {
root_dirs: &'a [PathBuf], root_dirs: &'a [PathBuf],
enable_snippets: bool, enable_snippets: bool,
) -> impl Iterator<Item = (LanguageServerName, Result<Arc<Client>>)> + 'a { ) -> impl Iterator<Item = (LanguageServerName, Result<Arc<Client>>)> + 'a {
language_config.language_servers.iter().map( language_config.language_servers.iter().filter_map(
move |LanguageServerFeatures { name, .. }| { move |LanguageServerFeatures { name, .. }| {
if let Some(clients) = self.inner.get(name) { if let Some(clients) = self.inner.get(name) {
if let Some((_, client)) = clients.iter().enumerate().find(|(i, client)| { if let Some((_, client)) = clients.iter().enumerate().find(|(i, client)| {
client.try_add_doc(&language_config.roots, root_dirs, doc_path, *i == 0) client.try_add_doc(&language_config.roots, root_dirs, doc_path, *i == 0)
}) { }) {
return (name.to_owned(), Ok(client.clone())); return Some((name.to_owned(), Ok(client.clone())));
} }
} }
match self.start_client( match self.start_client(
@ -780,13 +785,14 @@ impl Registry {
enable_snippets, enable_snippets,
) { ) {
Ok(client) => { Ok(client) => {
let client = client?;
self.inner self.inner
.entry(name.to_owned()) .entry(name.to_owned())
.or_default() .or_default()
.push(client.clone()); .push(client.clone());
(name.clone(), Ok(client)) Some((name.clone(), Ok(client)))
} }
Err(err) => (name.to_owned(), Err(err)), Err(err) => Some((name.to_owned(), Err(err))),
} }
}, },
) )
@ -887,18 +893,45 @@ fn start_client(
doc_path: Option<&std::path::PathBuf>, doc_path: Option<&std::path::PathBuf>,
root_dirs: &[PathBuf], root_dirs: &[PathBuf],
enable_snippets: bool, enable_snippets: bool,
) -> Result<NewClient> { ) -> Result<Option<NewClient>> {
let (workspace, workspace_is_cwd) = helix_loader::find_workspace();
let workspace = path::normalize(workspace);
let root = find_lsp_workspace(
doc_path
.and_then(|x| x.parent().and_then(|x| x.to_str()))
.unwrap_or("."),
&config.roots,
config.workspace_lsp_roots.as_deref().unwrap_or(root_dirs),
&workspace,
workspace_is_cwd,
);
// `root_uri` and `workspace_folder` can be empty in case there is no workspace
// `root_url` can not, use `workspace` as a fallback
let root_path = root.clone().unwrap_or_else(|| workspace.clone());
let root_uri = root.and_then(|root| lsp::Url::from_file_path(root).ok());
if let Some(globset) = &ls_config.required_root_patterns {
if !root_path
.read_dir()?
.flatten()
.map(|entry| entry.file_name())
.any(|entry| globset.is_match(entry))
{
return Ok(None);
}
}
let (client, incoming, initialize_notify) = Client::start( let (client, incoming, initialize_notify) = Client::start(
&ls_config.command, &ls_config.command,
&ls_config.args, &ls_config.args,
ls_config.config.clone(), ls_config.config.clone(),
ls_config.environment.clone(), ls_config.environment.clone(),
&config.roots, root_path,
config.workspace_lsp_roots.as_deref().unwrap_or(root_dirs), root_uri,
id, id,
name, name,
ls_config.timeout, ls_config.timeout,
doc_path,
)?; )?;
let client = Arc::new(client); let client = Arc::new(client);
@ -937,7 +970,7 @@ fn start_client(
initialize_notify.notify_one(); initialize_notify.notify_one();
}); });
Ok(NewClient(client, incoming)) Ok(Some(NewClient(client, incoming)))
} }
/// Find an LSP workspace of a file using the following mechanism: /// Find an LSP workspace of a file using the following mechanism:

@ -18,4 +18,4 @@ ropey = { version = "1.6.1", default-features = false }
which = "6.0" which = "6.0"
[dev-dependencies] [dev-dependencies]
tempfile = "3.9" tempfile = "3.10"

@ -42,6 +42,7 @@ signal-hook = "0.3"
tokio-stream = "0.1" tokio-stream = "0.1"
futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false } futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
arc-swap = { version = "1.6.0" } arc-swap = { version = "1.6.0" }
termini = "1"
# Logging # Logging
fern = "0.6" fern = "0.6"
@ -52,7 +53,7 @@ log = "0.4"
nucleo.workspace = true nucleo.workspace = true
ignore = "0.4" ignore = "0.4"
# markdown doc rendering # markdown doc rendering
pulldown-cmark = { version = "0.9", default-features = false } pulldown-cmark = { version = "0.10", default-features = false }
# file type detection # file type detection
content_inspector = "0.2.4" content_inspector = "0.2.4"
@ -61,7 +62,7 @@ open = "5.0.1"
url = "2.5.0" url = "2.5.0"
# config # config
toml = "0.7" toml = "0.8"
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
@ -72,7 +73,7 @@ grep-searcher = "0.1.13"
[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100 [target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100
signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] } signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
libc = "0.2.152" libc = "0.2.153"
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
crossterm = { version = "0.27", features = ["event-stream", "use-dev-tty"] } crossterm = { version = "0.27", features = ["event-stream", "use-dev-tty"] }
@ -83,4 +84,4 @@ helix-loader = { path = "../helix-loader" }
[dev-dependencies] [dev-dependencies]
smallvec = "1.13" smallvec = "1.13"
indoc = "2.0.4" indoc = "2.0.4"
tempfile = "3.9.0" tempfile = "3.10.0"

@ -6,4 +6,150 @@ fn main() {
build_grammars(Some(std::env::var("TARGET").unwrap())) build_grammars(Some(std::env::var("TARGET").unwrap()))
.expect("Failed to compile tree-sitter grammars"); .expect("Failed to compile tree-sitter grammars");
} }
#[cfg(windows)]
windows_rc::link_icon_in_windows_exe("../contrib/helix-256p.ico");
}
#[cfg(windows)]
mod windows_rc {
use std::io::prelude::Write;
use std::{env, io, path::Path, path::PathBuf, process};
pub(crate) fn link_icon_in_windows_exe(icon_path: &str) {
let rc_exe = find_rc_exe().expect("Windows SDK is to be installed along with MSVC");
let output = env::var("OUT_DIR").expect("Env var OUT_DIR should have been set by compiler");
let output_dir = PathBuf::from(output);
let rc_path = output_dir.join("resource.rc");
write_resource_file(&rc_path, icon_path).unwrap();
let resource_file = PathBuf::from(&output_dir).join("resource.lib");
compile_with_toolkit_msvc(rc_exe, resource_file, rc_path);
println!("cargo:rustc-link-search=native={}", output_dir.display());
println!("cargo:rustc-link-lib=dylib=resource");
}
fn compile_with_toolkit_msvc(rc_exe: PathBuf, output: PathBuf, input: PathBuf) {
let mut command = process::Command::new(rc_exe);
let command = command.arg(format!(
"/I{}",
env::var("CARGO_MANIFEST_DIR")
.expect("CARGO_MANIFEST_DIR should have been set by Cargo")
));
let status = command
.arg(format!("/fo{}", output.display()))
.arg(format!("{}", input.display()))
.output()
.unwrap();
println!(
"RC Output:\n{}\n------",
String::from_utf8_lossy(&status.stdout)
);
println!(
"RC Error:\n{}\n------",
String::from_utf8_lossy(&status.stderr)
);
}
fn find_rc_exe() -> io::Result<PathBuf> {
let find_reg_key = process::Command::new("reg")
.arg("query")
.arg(r"HKLM\SOFTWARE\Microsoft\Windows Kits\Installed Roots")
.arg("/reg:32")
.arg("/v")
.arg("KitsRoot10")
.output();
match find_reg_key {
Err(find_reg_key) => {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("Failed to run registry query: {}", find_reg_key),
))
}
Ok(find_reg_key) => {
if find_reg_key.status.code().unwrap() != 0 {
return Err(io::Error::new(
io::ErrorKind::Other,
"Can not find Windows SDK",
));
} else {
let lines = String::from_utf8(find_reg_key.stdout)
.expect("Should be able to parse the output");
let mut lines: Vec<&str> = lines.lines().collect();
let mut rc_exe_paths: Vec<PathBuf> = Vec::new();
lines.reverse();
for line in lines {
if line.trim().starts_with("KitsRoot") {
let kit: String = line
.chars()
.skip(line.find("REG_SZ").unwrap() + 6)
.skip_while(|c| c.is_whitespace())
.collect();
let p = PathBuf::from(&kit);
let rc = if cfg!(target_arch = "x86_64") {
p.join(r"bin\x64\rc.exe")
} else {
p.join(r"bin\x86\rc.exe")
};
if rc.exists() {
println!("{:?}", rc);
rc_exe_paths.push(rc.to_owned());
}
if let Ok(bin) = p.join("bin").read_dir() {
for e in bin.filter_map(|e| e.ok()) {
let p = if cfg!(target_arch = "x86_64") {
e.path().join(r"x64\rc.exe")
} else {
e.path().join(r"x86\rc.exe")
};
if p.exists() {
println!("{:?}", p);
rc_exe_paths.push(p.to_owned());
}
}
}
}
}
if rc_exe_paths.is_empty() {
return Err(io::Error::new(
io::ErrorKind::Other,
"Can not find Windows SDK",
));
}
println!("{:?}", rc_exe_paths);
let rc_path = rc_exe_paths.pop().unwrap();
let rc_exe = if !rc_path.exists() {
if cfg!(target_arch = "x86_64") {
PathBuf::from(rc_path.parent().unwrap()).join(r"bin\x64\rc.exe")
} else {
PathBuf::from(rc_path.parent().unwrap()).join(r"bin\x86\rc.exe")
}
} else {
rc_path
};
println!("Selected RC path: '{}'", rc_exe.display());
Ok(rc_exe)
}
}
}
}
fn write_resource_file(rc_path: &Path, icon_path: &str) -> io::Result<()> {
let mut f = std::fs::File::create(rc_path)?;
writeln!(f, "{} ICON \"{}\"", 1, icon_path)?;
Ok(())
}
} }

@ -21,7 +21,6 @@ use tui::backend::Backend;
use crate::{ use crate::{
args::Args, args::Args,
commands::apply_workspace_edit,
compositor::{Compositor, Event}, compositor::{Compositor, Event},
config::Config, config::Config,
handlers, handlers,
@ -67,7 +66,7 @@ pub struct Application {
#[allow(dead_code)] #[allow(dead_code)]
theme_loader: Arc<theme::Loader>, theme_loader: Arc<theme::Loader>,
#[allow(dead_code)] #[allow(dead_code)]
syn_loader: Arc<syntax::Loader>, syn_loader: Arc<ArcSwap<syntax::Loader>>,
signals: Signals, signals: Signals,
jobs: Jobs, jobs: Jobs,
@ -97,11 +96,7 @@ fn setup_integration_logging() {
} }
impl Application { impl Application {
pub fn new( pub fn new(args: Args, config: Config, lang_loader: syntax::Loader) -> Result<Self, Error> {
args: Args,
config: Config,
syn_loader_conf: syntax::Configuration,
) -> Result<Self, Error> {
#[cfg(feature = "integration")] #[cfg(feature = "integration")]
setup_integration_logging(); setup_integration_logging();
@ -127,7 +122,7 @@ impl Application {
}) })
.unwrap_or_else(|| theme_loader.default_theme(true_color)); .unwrap_or_else(|| theme_loader.default_theme(true_color));
let syn_loader = std::sync::Arc::new(syntax::Loader::new(syn_loader_conf)); let syn_loader = Arc::new(ArcSwap::from_pointee(lang_loader));
#[cfg(not(feature = "integration"))] #[cfg(not(feature = "integration"))]
let backend = CrosstermBackend::new(stdout(), &config.editor); let backend = CrosstermBackend::new(stdout(), &config.editor);
@ -395,10 +390,9 @@ impl Application {
/// refresh language config after config change /// refresh language config after config change
fn refresh_language_config(&mut self) -> Result<(), Error> { fn refresh_language_config(&mut self) -> Result<(), Error> {
let syntax_config = helix_core::config::user_syntax_loader() let lang_loader = helix_core::config::user_lang_loader()?;
.map_err(|err| anyhow::anyhow!("Failed to load language config: {}", err))?;
self.syn_loader = std::sync::Arc::new(syntax::Loader::new(syntax_config)); self.syn_loader.store(Arc::new(lang_loader));
self.editor.syn_loader = self.syn_loader.clone(); self.editor.syn_loader = self.syn_loader.clone();
for document in self.editor.documents.values_mut() { for document in self.editor.documents.values_mut() {
document.detect_language(self.syn_loader.clone()); document.detect_language(self.syn_loader.clone());
@ -573,26 +567,8 @@ impl Application {
let lines = doc_save_event.text.len_lines(); let lines = doc_save_event.text.len_lines();
let bytes = doc_save_event.text.len_bytes(); let bytes = doc_save_event.text.len_bytes();
if doc.path() != Some(&doc_save_event.path) { self.editor
doc.set_path(Some(&doc_save_event.path)); .set_doc_path(doc_save_event.doc_id, &doc_save_event.path);
let loader = self.editor.syn_loader.clone();
// borrowing the same doc again to get around the borrow checker
let doc = doc_mut!(self.editor, &doc_save_event.doc_id);
let id = doc.id();
doc.detect_language(loader);
self.editor.refresh_language_servers(id);
// and again a borrow checker workaround...
let doc = doc_mut!(self.editor, &doc_save_event.doc_id);
let diagnostics = Editor::doc_diagnostics(
&self.editor.language_servers,
&self.editor.diagnostics,
doc,
);
doc.replace_diagnostics(diagnostics, &[], None);
}
// TODO: fix being overwritten by lsp // TODO: fix being overwritten by lsp
self.editor.set_status(format!( self.editor.set_status(format!(
"'{}' written, {}L {}B", "'{}' written, {}L {}B",
@ -1011,11 +987,9 @@ impl Application {
let language_server = language_server!(); let language_server = language_server!();
if language_server.is_initialized() { if language_server.is_initialized() {
let offset_encoding = language_server.offset_encoding(); let offset_encoding = language_server.offset_encoding();
let res = apply_workspace_edit( let res = self
&mut self.editor, .editor
offset_encoding, .apply_workspace_edit(offset_encoding, &params.edit);
&params.edit,
);
Ok(json!(lsp::ApplyWorkspaceEditResponse { Ok(json!(lsp::ApplyWorkspaceEditResponse {
applied: res.is_ok(), applied: res.is_ok(),

@ -3653,7 +3653,7 @@ pub mod insert {
(pos, pos, local_offs) (pos, pos, local_offs)
}; };
let new_range = if doc.restore_cursor { let new_range = if range.cursor(text) > range.anchor {
// when appending, extend the range by local_offs // when appending, extend the range by local_offs
Range::new( Range::new(
range.anchor + global_offs, range.anchor + global_offs,
@ -4086,6 +4086,7 @@ fn replace_with_yanked_impl(editor: &mut Editor, register: char, count: usize) {
return; return;
}; };
let values: Vec<_> = values.map(|value| value.to_string()).collect(); let values: Vec<_> = values.map(|value| value.to_string()).collect();
let scrolloff = editor.config().scrolloff;
let (view, doc) = current!(editor); let (view, doc) = current!(editor);
let repeat = std::iter::repeat( let repeat = std::iter::repeat(
@ -4108,6 +4109,8 @@ fn replace_with_yanked_impl(editor: &mut Editor, register: char, count: usize) {
}); });
doc.apply(&transaction, view.id); doc.apply(&transaction, view.id);
doc.append_changes_to_history(view);
view.ensure_cursor_in_view(doc, scrolloff);
} }
fn replace_selections_with_clipboard(cx: &mut Context) { fn replace_selections_with_clipboard(cx: &mut Context) {

@ -729,8 +729,7 @@ pub fn code_action(cx: &mut Context) {
resolved_code_action.as_ref().unwrap_or(code_action); resolved_code_action.as_ref().unwrap_or(code_action);
if let Some(ref workspace_edit) = resolved_code_action.edit { if let Some(ref workspace_edit) = resolved_code_action.edit {
log::debug!("edit: {:?}", workspace_edit); let _ = editor.apply_workspace_edit(offset_encoding, workspace_edit);
let _ = apply_workspace_edit(editor, offset_encoding, workspace_edit);
} }
// if code action provides both edit and command first the edit // if code action provides both edit and command first the edit
@ -790,63 +789,6 @@ pub fn execute_lsp_command(editor: &mut Editor, language_server_id: usize, cmd:
}); });
} }
pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> {
use lsp::ResourceOp;
use std::fs;
match op {
ResourceOp::Create(op) => {
let path = op.uri.to_file_path().unwrap();
let ignore_if_exists = op.options.as_ref().map_or(false, |options| {
!options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false)
});
if ignore_if_exists && path.exists() {
Ok(())
} else {
// Create directory if it does not exist
if let Some(dir) = path.parent() {
if !dir.is_dir() {
fs::create_dir_all(dir)?;
}
}
fs::write(&path, [])
}
}
ResourceOp::Delete(op) => {
let path = op.uri.to_file_path().unwrap();
if path.is_dir() {
let recursive = op
.options
.as_ref()
.and_then(|options| options.recursive)
.unwrap_or(false);
if recursive {
fs::remove_dir_all(&path)
} else {
fs::remove_dir(&path)
}
} else if path.is_file() {
fs::remove_file(&path)
} else {
Ok(())
}
}
ResourceOp::Rename(op) => {
let from = op.old_uri.to_file_path().unwrap();
let to = op.new_uri.to_file_path().unwrap();
let ignore_if_exists = op.options.as_ref().map_or(false, |options| {
!options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false)
});
if ignore_if_exists && to.exists() {
Ok(())
} else {
fs::rename(from, &to)
}
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct ApplyEditError { pub struct ApplyEditError {
pub kind: ApplyEditErrorKind, pub kind: ApplyEditErrorKind,
@ -874,142 +816,6 @@ impl ToString for ApplyEditErrorKind {
} }
} }
///TODO make this transactional (and set failureMode to transactional)
pub fn apply_workspace_edit(
editor: &mut Editor,
offset_encoding: OffsetEncoding,
workspace_edit: &lsp::WorkspaceEdit,
) -> Result<(), ApplyEditError> {
let mut apply_edits = |uri: &helix_lsp::Url,
version: Option<i32>,
text_edits: Vec<lsp::TextEdit>|
-> Result<(), ApplyEditErrorKind> {
let path = match uri.to_file_path() {
Ok(path) => path,
Err(_) => {
let err = format!("unable to convert URI to filepath: {}", uri);
log::error!("{}", err);
editor.set_error(err);
return Err(ApplyEditErrorKind::UnknownURISchema);
}
};
let doc_id = match editor.open(&path, Action::Load) {
Ok(doc_id) => doc_id,
Err(err) => {
let err = format!("failed to open document: {}: {}", uri, err);
log::error!("{}", err);
editor.set_error(err);
return Err(ApplyEditErrorKind::FileNotFound);
}
};
let doc = doc!(editor, &doc_id);
if let Some(version) = version {
if version != doc.version() {
let err = format!("outdated workspace edit for {path:?}");
log::error!("{err}, expected {} but got {version}", doc.version());
editor.set_error(err);
return Err(ApplyEditErrorKind::DocumentChanged);
}
}
// Need to determine a view for apply/append_changes_to_history
let view_id = editor.get_synced_view_id(doc_id);
let doc = doc_mut!(editor, &doc_id);
let transaction = helix_lsp::util::generate_transaction_from_edits(
doc.text(),
text_edits,
offset_encoding,
);
let view = view_mut!(editor, view_id);
doc.apply(&transaction, view.id);
doc.append_changes_to_history(view);
Ok(())
};
if let Some(ref document_changes) = workspace_edit.document_changes {
match document_changes {
lsp::DocumentChanges::Edits(document_edits) => {
for (i, document_edit) in document_edits.iter().enumerate() {
let edits = document_edit
.edits
.iter()
.map(|edit| match edit {
lsp::OneOf::Left(text_edit) => text_edit,
lsp::OneOf::Right(annotated_text_edit) => {
&annotated_text_edit.text_edit
}
})
.cloned()
.collect();
apply_edits(
&document_edit.text_document.uri,
document_edit.text_document.version,
edits,
)
.map_err(|kind| ApplyEditError {
kind,
failed_change_idx: i,
})?;
}
}
lsp::DocumentChanges::Operations(operations) => {
log::debug!("document changes - operations: {:?}", operations);
for (i, operation) in operations.iter().enumerate() {
match operation {
lsp::DocumentChangeOperation::Op(op) => {
apply_document_resource_op(op).map_err(|io| ApplyEditError {
kind: ApplyEditErrorKind::IoError(io),
failed_change_idx: i,
})?;
}
lsp::DocumentChangeOperation::Edit(document_edit) => {
let edits = document_edit
.edits
.iter()
.map(|edit| match edit {
lsp::OneOf::Left(text_edit) => text_edit,
lsp::OneOf::Right(annotated_text_edit) => {
&annotated_text_edit.text_edit
}
})
.cloned()
.collect();
apply_edits(
&document_edit.text_document.uri,
document_edit.text_document.version,
edits,
)
.map_err(|kind| ApplyEditError {
kind,
failed_change_idx: i,
})?;
}
}
}
}
}
return Ok(());
}
if let Some(ref changes) = workspace_edit.changes {
log::debug!("workspace changes: {:?}", changes);
for (i, (uri, text_edits)) in changes.iter().enumerate() {
let text_edits = text_edits.to_vec();
apply_edits(uri, None, text_edits).map_err(|kind| ApplyEditError {
kind,
failed_change_idx: i,
})?;
}
}
Ok(())
}
/// Precondition: `locations` should be non-empty. /// Precondition: `locations` should be non-empty.
fn goto_impl( fn goto_impl(
editor: &mut Editor, editor: &mut Editor,
@ -1323,7 +1129,7 @@ pub fn rename_symbol(cx: &mut Context) {
match block_on(future) { match block_on(future) {
Ok(edits) => { Ok(edits) => {
let _ = apply_workspace_edit(cx.editor, offset_encoding, &edits); let _ = cx.editor.apply_workspace_edit(offset_encoding, &edits);
} }
Err(err) => cx.editor.set_error(err.to_string()), Err(err) => cx.editor.set_error(err.to_string()),
} }

@ -8,7 +8,6 @@ use super::*;
use helix_core::fuzzy::fuzzy_match; use helix_core::fuzzy::fuzzy_match;
use helix_core::indent::MAX_INDENT; use helix_core::indent::MAX_INDENT;
use helix_core::{encoding, line_ending, shellwords::Shellwords}; use helix_core::{encoding, line_ending, shellwords::Shellwords};
use helix_lsp::{OffsetEncoding, Url};
use helix_view::document::DEFAULT_LANGUAGE_NAME; use helix_view::document::DEFAULT_LANGUAGE_NAME;
use helix_view::editor::{Action, CloseError, ConfigEvent}; use helix_view::editor::{Action, CloseError, ConfigEvent};
use serde_json::Value; use serde_json::Value;
@ -1546,10 +1545,7 @@ fn tree_sitter_highlight_name(
let text = doc.text().slice(..); let text = doc.text().slice(..);
let cursor = doc.selection(view.id).primary().cursor(text); let cursor = doc.selection(view.id).primary().cursor(text);
let byte = text.char_to_byte(cursor); let byte = text.char_to_byte(cursor);
let node = syntax let node = syntax.descendant_for_byte_range(byte, byte)?;
.tree()
.root_node()
.descendant_for_byte_range(byte, byte)?;
// Query the same range as the one used in syntax highlighting. // Query the same range as the one used in syntax highlighting.
let range = { let range = {
// Calculate viewport byte ranges: // Calculate viewport byte ranges:
@ -2407,67 +2403,14 @@ fn move_buffer(
ensure!(args.len() == 1, format!(":move takes one argument")); ensure!(args.len() == 1, format!(":move takes one argument"));
let doc = doc!(cx.editor); let doc = doc!(cx.editor);
let new_path =
helix_stdx::path::canonicalize(&PathBuf::from(args.first().unwrap().to_string()));
let old_path = doc let old_path = doc
.path() .path()
.ok_or_else(|| anyhow!("Scratch buffer cannot be moved. Use :write instead"))? .context("Scratch buffer cannot be moved. Use :write instead")?
.clone(); .clone();
let old_path_as_url = doc.url().unwrap(); let new_path = args.first().unwrap().to_string();
let new_path_as_url = Url::from_file_path(&new_path).unwrap(); if let Err(err) = cx.editor.move_path(&old_path, new_path.as_ref()) {
bail!("Could not move file: {err}");
let edits: Vec<(
helix_lsp::Result<helix_lsp::lsp::WorkspaceEdit>,
OffsetEncoding,
String,
)> = doc
.language_servers()
.map(|lsp| {
(
lsp.prepare_file_rename(&old_path_as_url, &new_path_as_url),
lsp.offset_encoding(),
lsp.name().to_owned(),
)
})
.filter(|(f, _, _)| f.is_some())
.map(|(f, encoding, name)| (helix_lsp::block_on(f.unwrap()), encoding, name))
.collect();
for (lsp_reply, encoding, name) in edits {
match lsp_reply {
Ok(edit) => {
if let Err(e) = apply_workspace_edit(cx.editor, encoding, &edit) {
log::error!(
":move command failed to apply edits from lsp {}: {:?}",
name,
e
);
};
}
Err(e) => {
log::error!("LSP {} failed to treat willRename request: {:?}", name, e);
}
};
} }
let doc = doc_mut!(cx.editor);
doc.set_path(Some(new_path.as_path()));
if let Err(e) = std::fs::rename(&old_path, &new_path) {
doc.set_path(Some(old_path.as_path()));
bail!("Could not move file: {}", e);
};
doc.language_servers().for_each(|lsp| {
lsp.did_file_rename(&old_path_as_url, &new_path_as_url);
});
cx.editor
.language_servers
.file_event_handler
.file_changed(new_path);
Ok(()) Ok(())
} }

@ -2,7 +2,7 @@ use crossterm::{
style::{Color, Print, Stylize}, style::{Color, Print, Stylize},
tty::IsTty, tty::IsTty,
}; };
use helix_core::config::{default_syntax_loader, user_syntax_loader}; use helix_core::config::{default_lang_config, user_lang_config};
use helix_loader::grammar::load_runtime_file; use helix_loader::grammar::load_runtime_file;
use helix_view::clipboard::get_clipboard_provider; use helix_view::clipboard::get_clipboard_provider;
use std::io::Write; use std::io::Write;
@ -128,7 +128,7 @@ pub fn languages_all() -> std::io::Result<()> {
let stdout = std::io::stdout(); let stdout = std::io::stdout();
let mut stdout = stdout.lock(); let mut stdout = stdout.lock();
let mut syn_loader_conf = match user_syntax_loader() { let mut syn_loader_conf = match user_lang_config() {
Ok(conf) => conf, Ok(conf) => conf,
Err(err) => { Err(err) => {
let stderr = std::io::stderr(); let stderr = std::io::stderr();
@ -141,7 +141,7 @@ pub fn languages_all() -> std::io::Result<()> {
err err
)?; )?;
writeln!(stderr, "{}", "Using default language config".yellow())?; writeln!(stderr, "{}", "Using default language config".yellow())?;
default_syntax_loader() default_lang_config()
} }
}; };
@ -234,7 +234,7 @@ pub fn language(lang_str: String) -> std::io::Result<()> {
let stdout = std::io::stdout(); let stdout = std::io::stdout();
let mut stdout = stdout.lock(); let mut stdout = stdout.lock();
let syn_loader_conf = match user_syntax_loader() { let syn_loader_conf = match user_lang_config() {
Ok(conf) => conf, Ok(conf) => conf,
Err(err) => { Err(err) => {
let stderr = std::io::stderr(); let stderr = std::io::stderr();
@ -247,7 +247,7 @@ pub fn language(lang_str: String) -> std::io::Result<()> {
err err
)?; )?;
writeln!(stderr, "{}", "Using default language config".yellow())?; writeln!(stderr, "{}", "Using default language config".yellow())?;
default_syntax_loader() default_lang_config()
} }
}; };

@ -22,17 +22,30 @@ use url::Url;
pub use keymap::macros::*; pub use keymap::macros::*;
#[cfg(not(windows))]
fn true_color() -> bool {
std::env::var("COLORTERM")
.map(|v| matches!(v.as_str(), "truecolor" | "24bit"))
.unwrap_or(false)
}
#[cfg(windows)] #[cfg(windows)]
fn true_color() -> bool { fn true_color() -> bool {
true true
} }
#[cfg(not(windows))]
fn true_color() -> bool {
if matches!(
std::env::var("COLORTERM").map(|v| matches!(v.as_str(), "truecolor" | "24bit")),
Ok(true)
) {
return true;
}
match termini::TermInfo::from_env() {
Ok(t) => {
t.extended_cap("RGB").is_some()
|| t.extended_cap("Tc").is_some()
|| (t.extended_cap("setrgbf").is_some() && t.extended_cap("setrgbb").is_some())
}
Err(_) => false,
}
}
/// Function used for filtering dir entries in the various file pickers. /// Function used for filtering dir entries in the various file pickers.
fn filter_picker_entry(entry: &DirEntry, root: &Path, dedup_symlinks: bool) -> bool { fn filter_picker_entry(entry: &DirEntry, root: &Path, dedup_symlinks: bool) -> bool {
// We always want to ignore the .git directory, otherwise if // We always want to ignore the .git directory, otherwise if

@ -145,18 +145,18 @@ FLAGS:
} }
}; };
let syn_loader_conf = helix_core::config::user_syntax_loader().unwrap_or_else(|err| { let lang_loader = helix_core::config::user_lang_loader().unwrap_or_else(|err| {
eprintln!("Bad language config: {}", err); eprintln!("{}", err);
eprintln!("Press <ENTER> to continue with default language config"); eprintln!("Press <ENTER> to continue with default language config");
use std::io::Read; use std::io::Read;
// This waits for an enter press. // This waits for an enter press.
let _ = std::io::stdin().read(&mut []); let _ = std::io::stdin().read(&mut []);
helix_core::config::default_syntax_loader() helix_core::config::default_lang_loader()
}); });
// TODO: use the thread local executor to spawn the application task separately from the work pool // TODO: use the thread local executor to spawn the application task separately from the work pool
let mut app = Application::new(args, config, syn_loader_conf) let mut app =
.context("unable to create new application")?; Application::new(args, config, lang_loader).context("unable to create new application")?;
let exit_code = app.run(&mut EventStream::new()).await?; let exit_code = app.run(&mut EventStream::new()).await?;

@ -1,5 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use arc_swap::ArcSwap;
use helix_core::syntax; use helix_core::syntax;
use helix_view::graphics::{Margin, Rect, Style}; use helix_view::graphics::{Margin, Rect, Style};
use tui::buffer::Buffer; use tui::buffer::Buffer;
@ -18,13 +19,17 @@ pub struct SignatureHelp {
active_param_range: Option<(usize, usize)>, active_param_range: Option<(usize, usize)>,
language: String, language: String,
config_loader: Arc<syntax::Loader>, config_loader: Arc<ArcSwap<syntax::Loader>>,
} }
impl SignatureHelp { impl SignatureHelp {
pub const ID: &'static str = "signature-help"; pub const ID: &'static str = "signature-help";
pub fn new(signature: String, language: String, config_loader: Arc<syntax::Loader>) -> Self { pub fn new(
signature: String,
language: String,
config_loader: Arc<ArcSwap<syntax::Loader>>,
) -> Self {
Self { Self {
signature, signature,
signature_doc: None, signature_doc: None,

@ -1,4 +1,5 @@
use crate::compositor::{Component, Context}; use crate::compositor::{Component, Context};
use arc_swap::ArcSwap;
use tui::{ use tui::{
buffer::Buffer as Surface, buffer::Buffer as Surface,
text::{Span, Spans, Text}, text::{Span, Spans, Text},
@ -6,7 +7,7 @@ use tui::{
use std::sync::Arc; use std::sync::Arc;
use pulldown_cmark::{CodeBlockKind, Event, HeadingLevel, Options, Parser, Tag}; use pulldown_cmark::{CodeBlockKind, Event, HeadingLevel, Options, Parser, Tag, TagEnd};
use helix_core::{ use helix_core::{
syntax::{self, HighlightEvent, InjectionLanguageMarker, Syntax}, syntax::{self, HighlightEvent, InjectionLanguageMarker, Syntax},
@ -31,7 +32,7 @@ pub fn highlighted_code_block<'a>(
text: &str, text: &str,
language: &str, language: &str,
theme: Option<&Theme>, theme: Option<&Theme>,
config_loader: Arc<syntax::Loader>, config_loader: Arc<ArcSwap<syntax::Loader>>,
additional_highlight_spans: Option<Vec<(usize, std::ops::Range<usize>)>>, additional_highlight_spans: Option<Vec<(usize, std::ops::Range<usize>)>>,
) -> Text<'a> { ) -> Text<'a> {
let mut spans = Vec::new(); let mut spans = Vec::new();
@ -48,6 +49,7 @@ pub fn highlighted_code_block<'a>(
let ropeslice = RopeSlice::from(text); let ropeslice = RopeSlice::from(text);
let syntax = config_loader let syntax = config_loader
.load()
.language_configuration_for_injection_string(&InjectionLanguageMarker::Name( .language_configuration_for_injection_string(&InjectionLanguageMarker::Name(
language.into(), language.into(),
)) ))
@ -121,7 +123,7 @@ pub fn highlighted_code_block<'a>(
pub struct Markdown { pub struct Markdown {
contents: String, contents: String,
config_loader: Arc<syntax::Loader>, config_loader: Arc<ArcSwap<syntax::Loader>>,
} }
// TODO: pre-render and self reference via Pin // TODO: pre-render and self reference via Pin
@ -140,7 +142,7 @@ impl Markdown {
]; ];
const INDENT: &'static str = " "; const INDENT: &'static str = " ";
pub fn new(contents: String, config_loader: Arc<syntax::Loader>) -> Self { pub fn new(contents: String, config_loader: Arc<ArcSwap<syntax::Loader>>) -> Self {
Self { Self {
contents, contents,
config_loader, config_loader,
@ -209,7 +211,7 @@ impl Markdown {
list_stack.push(list); list_stack.push(list);
} }
Event::End(Tag::List(_)) => { Event::End(TagEnd::List(_)) => {
list_stack.pop(); list_stack.pop();
// whenever top-level list closes, empty line // whenever top-level list closes, empty line
@ -249,7 +251,10 @@ impl Markdown {
Event::End(tag) => { Event::End(tag) => {
tags.pop(); tags.pop();
match tag { match tag {
Tag::Heading(_, _, _) | Tag::Paragraph | Tag::CodeBlock(_) | Tag::Item => { TagEnd::Heading(_)
| TagEnd::Paragraph
| TagEnd::CodeBlock
| TagEnd::Item => {
push_line(&mut spans, &mut lines); push_line(&mut spans, &mut lines);
} }
_ => (), _ => (),
@ -257,7 +262,7 @@ impl Markdown {
// whenever heading, code block or paragraph closes, empty line // whenever heading, code block or paragraph closes, empty line
match tag { match tag {
Tag::Heading(_, _, _) | Tag::Paragraph | Tag::CodeBlock(_) => { TagEnd::Heading(_) | TagEnd::Paragraph | TagEnd::CodeBlock => {
lines.push(Spans::default()); lines.push(Spans::default());
} }
_ => (), _ => (),
@ -279,7 +284,7 @@ impl Markdown {
lines.extend(tui_text.lines.into_iter()); lines.extend(tui_text.lines.into_iter());
} else { } else {
let style = match tags.last() { let style = match tags.last() {
Some(Tag::Heading(level, ..)) => match level { Some(Tag::Heading { level, .. }) => match level {
HeadingLevel::H1 => heading_styles[0], HeadingLevel::H1 => heading_styles[0],
HeadingLevel::H2 => heading_styles[1], HeadingLevel::H2 => heading_styles[1],
HeadingLevel::H3 => heading_styles[2], HeadingLevel::H3 => heading_styles[2],

@ -427,6 +427,7 @@ impl<T: Item + 'static> Component for Menu<T> {
cell.set_fg(scroll_style.fg.unwrap_or(helix_view::theme::Color::Reset)); cell.set_fg(scroll_style.fg.unwrap_or(helix_view::theme::Color::Reset));
} else if !render_borders { } else if !render_borders {
// Draw scroll track // Draw scroll track
cell.set_symbol(half_block);
cell.set_fg(scroll_style.bg.unwrap_or(helix_view::theme::Color::Reset)); cell.set_fg(scroll_style.bg.unwrap_or(helix_view::theme::Color::Reset));
} }
} }

@ -336,8 +336,8 @@ pub mod completers {
pub fn language(editor: &Editor, input: &str) -> Vec<Completion> { pub fn language(editor: &Editor, input: &str) -> Vec<Completion> {
let text: String = "text".into(); let text: String = "text".into();
let language_ids = editor let loader = editor.syn_loader.load();
.syn_loader let language_ids = loader
.language_configs() .language_configs()
.map(|config| &config.language_id) .map(|config| &config.language_id)
.chain(std::iter::once(&text)); .chain(std::iter::once(&text));

@ -461,14 +461,17 @@ impl<T: Item + 'static> Picker<T> {
// Then attempt to highlight it if it has no language set // Then attempt to highlight it if it has no language set
if doc.language_config().is_none() { if doc.language_config().is_none() {
if let Some(language_config) = doc.detect_language_config(&cx.editor.syn_loader) { if let Some(language_config) = doc.detect_language_config(&cx.editor.syn_loader.load())
{
doc.language = Some(language_config.clone()); doc.language = Some(language_config.clone());
let text = doc.text().clone(); let text = doc.text().clone();
let loader = cx.editor.syn_loader.clone(); let loader = cx.editor.syn_loader.clone();
let job = tokio::task::spawn_blocking(move || { let job = tokio::task::spawn_blocking(move || {
let syntax = language_config.highlight_config(&loader.scopes()).and_then( let syntax = language_config
|highlight_config| Syntax::new(text.slice(..), highlight_config, loader), .highlight_config(&loader.load().scopes())
); .and_then(|highlight_config| {
Syntax::new(text.slice(..), highlight_config, loader)
});
let callback = move |editor: &mut Editor, compositor: &mut Compositor| { let callback = move |editor: &mut Editor, compositor: &mut Compositor| {
let Some(syntax) = syntax else { let Some(syntax) = syntax else {
log::info!("highlighting picker item failed"); log::info!("highlighting picker item failed");

@ -303,6 +303,7 @@ impl<T: Component> Component for Popup<T> {
cell.set_fg(scroll_style.fg.unwrap_or(helix_view::theme::Color::Reset)); cell.set_fg(scroll_style.fg.unwrap_or(helix_view::theme::Color::Reset));
} else if !render_borders { } else if !render_borders {
// Draw scroll track // Draw scroll track
cell.set_symbol(half_block);
cell.set_fg(scroll_style.bg.unwrap_or(helix_view::theme::Color::Reset)); cell.set_fg(scroll_style.bg.unwrap_or(helix_view::theme::Color::Reset));
} }
} }

@ -1,5 +1,6 @@
use crate::compositor::{Component, Compositor, Context, Event, EventResult}; use crate::compositor::{Component, Compositor, Context, Event, EventResult};
use crate::{alt, ctrl, key, shift, ui}; use crate::{alt, ctrl, key, shift, ui};
use arc_swap::ArcSwap;
use helix_core::syntax; use helix_core::syntax;
use helix_view::input::KeyEvent; use helix_view::input::KeyEvent;
use helix_view::keyboard::KeyCode; use helix_view::keyboard::KeyCode;
@ -34,7 +35,7 @@ pub struct Prompt {
callback_fn: CallbackFn, callback_fn: CallbackFn,
pub doc_fn: DocFn, pub doc_fn: DocFn,
next_char_handler: Option<PromptCharHandler>, next_char_handler: Option<PromptCharHandler>,
language: Option<(&'static str, Arc<syntax::Loader>)>, language: Option<(&'static str, Arc<ArcSwap<syntax::Loader>>)>,
} }
#[derive(Clone, Copy, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq)]
@ -98,7 +99,11 @@ impl Prompt {
self self
} }
pub fn with_language(mut self, language: &'static str, loader: Arc<syntax::Loader>) -> Self { pub fn with_language(
mut self,
language: &'static str,
loader: Arc<ArcSwap<syntax::Loader>>,
) -> Self {
self.language = Some((language, loader)); self.language = Some((language, loader));
self self
} }
@ -393,7 +398,7 @@ impl Prompt {
height, height,
); );
if !self.completion.is_empty() { if completion_area.height > 0 && !self.completion.is_empty() {
let area = completion_area; let area = completion_area;
let background = theme.get("ui.menu"); let background = theme.get("ui.menu");

@ -315,7 +315,7 @@ async fn test_write_auto_format_fails_still_writes() -> anyhow::Result<()> {
let mut app = helpers::AppBuilder::new() let mut app = helpers::AppBuilder::new()
.with_file(file.path(), None) .with_file(file.path(), None)
.with_input_text("#[l|]#et foo = 0;\n") .with_input_text("#[l|]#et foo = 0;\n")
.with_lang_config(helpers::test_syntax_conf(Some(lang_conf.into()))) .with_lang_loader(helpers::test_syntax_loader(Some(lang_conf.into())))
.build()?; .build()?;
test_key_sequences(&mut app, vec![(Some(":w<ret>"), None)], false).await?; test_key_sequences(&mut app, vec![(Some(":w<ret>"), None)], false).await?;

@ -139,7 +139,7 @@ pub async fn test_key_sequence_with_input_text<T: Into<TestCase>>(
let test_case = test_case.into(); let test_case = test_case.into();
let mut app = match app { let mut app = match app {
Some(app) => app, Some(app) => app,
None => Application::new(Args::default(), test_config(), test_syntax_conf(None))?, None => Application::new(Args::default(), test_config(), test_syntax_loader(None))?,
}; };
let (view, doc) = helix_view::current!(app.editor); let (view, doc) = helix_view::current!(app.editor);
@ -162,9 +162,9 @@ pub async fn test_key_sequence_with_input_text<T: Into<TestCase>>(
.await .await
} }
/// Generates language configs that merge in overrides, like a user language /// Generates language config loader that merge in overrides, like a user language
/// config. The argument string must be a raw TOML document. /// config. The argument string must be a raw TOML document.
pub fn test_syntax_conf(overrides: Option<String>) -> helix_core::syntax::Configuration { pub fn test_syntax_loader(overrides: Option<String>) -> helix_core::syntax::Loader {
let mut lang = helix_loader::config::default_lang_config(); let mut lang = helix_loader::config::default_lang_config();
if let Some(overrides) = overrides { if let Some(overrides) = overrides {
@ -172,7 +172,7 @@ pub fn test_syntax_conf(overrides: Option<String>) -> helix_core::syntax::Config
lang = helix_loader::merge_toml_values(lang, override_toml, 3); lang = helix_loader::merge_toml_values(lang, override_toml, 3);
} }
lang.try_into().unwrap() helix_core::syntax::Loader::new(lang.try_into().unwrap()).unwrap()
} }
/// Use this for very simple test cases where there is one input /// Use this for very simple test cases where there is one input
@ -271,7 +271,7 @@ pub fn new_readonly_tempfile() -> anyhow::Result<NamedTempFile> {
pub struct AppBuilder { pub struct AppBuilder {
args: Args, args: Args,
config: Config, config: Config,
syn_conf: helix_core::syntax::Configuration, syn_loader: helix_core::syntax::Loader,
input: Option<(String, Selection)>, input: Option<(String, Selection)>,
} }
@ -280,7 +280,7 @@ impl Default for AppBuilder {
Self { Self {
args: Args::default(), args: Args::default(),
config: test_config(), config: test_config(),
syn_conf: test_syntax_conf(None), syn_loader: test_syntax_loader(None),
input: None, input: None,
} }
} }
@ -314,8 +314,8 @@ impl AppBuilder {
self self
} }
pub fn with_lang_config(mut self, syn_conf: helix_core::syntax::Configuration) -> Self { pub fn with_lang_loader(mut self, syn_loader: helix_core::syntax::Loader) -> Self {
self.syn_conf = syn_conf; self.syn_loader = syn_loader;
self self
} }
@ -328,7 +328,7 @@ impl AppBuilder {
bail!("Having the directory {path:?} in args.files[0] is not yet supported for integration tests"); bail!("Having the directory {path:?} in args.files[0] is not yet supported for integration tests");
} }
let mut app = Application::new(self.args, self.config, self.syn_conf)?; let mut app = Application::new(self.args, self.config, self.syn_loader)?;
if let Some((text, selection)) = self.input { if let Some((text, selection)) = self.input {
let (view, doc) = helix_view::current!(app.editor); let (view, doc) = helix_view::current!(app.editor);

@ -20,7 +20,7 @@ helix-core = { path = "../helix-core" }
bitflags = "2.4" bitflags = "2.4"
cassowary = "0.3" cassowary = "0.3"
unicode-segmentation = "1.10" unicode-segmentation = "1.11"
crossterm = { version = "0.27", optional = true } crossterm = { version = "0.27", optional = true }
termini = "1.0" termini = "1.0"
serde = { version = "1", "optional" = true, features = ["derive"]} serde = { version = "1", "optional" = true, features = ["derive"]}

@ -19,7 +19,7 @@ tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync", "p
parking_lot = "0.12" parking_lot = "0.12"
arc-swap = { version = "1.6.0" } arc-swap = { version = "1.6.0" }
gix = { version = "0.58.0", default-features = false , optional = true } gix = { version = "0.58.0", features = ["attributes"], default-features = false, optional = true }
imara-diff = "0.1.5" imara-diff = "0.1.5"
anyhow = "1" anyhow = "1"
@ -29,4 +29,4 @@ log = "0.4"
git = ["gix"] git = ["gix"]
[dev-dependencies] [dev-dependencies]
tempfile = "3.9" tempfile = "3.10"

@ -1,5 +1,7 @@
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use arc_swap::ArcSwap; use arc_swap::ArcSwap;
use gix::filter::plumbing::driver::apply::Delay;
use std::io::Read;
use std::path::Path; use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
@ -76,29 +78,21 @@ impl DiffProvider for Git {
let file_oid = find_file_in_commit(&repo, &head, file)?; let file_oid = find_file_in_commit(&repo, &head, file)?;
let file_object = repo.find_object(file_oid)?; let file_object = repo.find_object(file_oid)?;
let mut data = file_object.detach().data; let data = file_object.detach().data;
// convert LF to CRLF if configured to avoid showing every line as changed // Get the actual data that git would make out of the git object.
if repo // This will apply the user's git config or attributes like crlf conversions.
.config_snapshot() if let Some(work_dir) = repo.work_dir() {
.boolean("core.autocrlf") let rela_path = file.strip_prefix(work_dir)?;
.unwrap_or(false) let rela_path = gix::path::try_into_bstr(rela_path)?;
{ let (mut pipeline, _) = repo.filter_pipeline(None)?;
let mut normalized_file = Vec::with_capacity(data.len()); let mut worktree_outcome =
let mut at_cr = false; pipeline.convert_to_worktree(&data, rela_path.as_ref(), Delay::Forbid)?;
for &byte in &data { let mut buf = Vec::with_capacity(data.len());
if byte == b'\n' { worktree_outcome.read_to_end(&mut buf)?;
// if this is a LF instead of a CRLF (last byte was not a CR) Ok(buf)
// insert a new CR to generate a CRLF } else {
if !at_cr { Ok(data)
normalized_file.push(b'\r');
}
}
at_cr = byte == b'\r';
normalized_file.push(byte)
}
data = normalized_file
} }
Ok(data)
} }
fn get_current_head_name(&self, file: &Path) -> Result<Arc<ArcSwap<Box<str>>>> { fn get_current_head_name(&self, file: &Path) -> Result<Arc<ArcSwap<Box<str>>>> {

@ -43,14 +43,14 @@ chardetng = "0.1"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
toml = "0.7" toml = "0.8"
log = "~0.4" log = "~0.4"
parking_lot = "0.12.1" parking_lot = "0.12.1"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
clipboard-win = { version = "5.0", features = ["std"] } clipboard-win = { version = "5.1", features = ["std"] }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
libc = "0.2" libc = "0.2"

@ -681,7 +681,7 @@ impl Document {
pub fn open( pub fn open(
path: &Path, path: &Path,
encoding: Option<&'static Encoding>, encoding: Option<&'static Encoding>,
config_loader: Option<Arc<syntax::Loader>>, config_loader: Option<Arc<ArcSwap<syntax::Loader>>>,
config: Arc<dyn DynAccess<Config>>, config: Arc<dyn DynAccess<Config>>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
// Open the file if it exists, otherwise assume it is a new file (and thus empty). // Open the file if it exists, otherwise assume it is a new file (and thus empty).
@ -922,10 +922,11 @@ impl Document {
} }
/// Detect the programming language based on the file type. /// Detect the programming language based on the file type.
pub fn detect_language(&mut self, config_loader: Arc<syntax::Loader>) { pub fn detect_language(&mut self, config_loader: Arc<ArcSwap<syntax::Loader>>) {
let loader = config_loader.load();
self.set_language( self.set_language(
self.detect_language_config(&config_loader), self.detect_language_config(&loader),
Some(config_loader), Some(Arc::clone(&config_loader)),
); );
} }
@ -1041,6 +1042,9 @@ impl Document {
self.encoding self.encoding
} }
/// sets the document path without sending events to various
/// observers (like LSP), in most cases `Editor::set_doc_path`
/// should be used instead
pub fn set_path(&mut self, path: Option<&Path>) { pub fn set_path(&mut self, path: Option<&Path>) {
let path = path.map(helix_stdx::path::canonicalize); let path = path.map(helix_stdx::path::canonicalize);
@ -1056,10 +1060,12 @@ impl Document {
pub fn set_language( pub fn set_language(
&mut self, &mut self,
language_config: Option<Arc<helix_core::syntax::LanguageConfiguration>>, language_config: Option<Arc<helix_core::syntax::LanguageConfiguration>>,
loader: Option<Arc<helix_core::syntax::Loader>>, loader: Option<Arc<ArcSwap<helix_core::syntax::Loader>>>,
) { ) {
if let (Some(language_config), Some(loader)) = (language_config, loader) { if let (Some(language_config), Some(loader)) = (language_config, loader) {
if let Some(highlight_config) = language_config.highlight_config(&loader.scopes()) { if let Some(highlight_config) =
language_config.highlight_config(&(*loader).load().scopes())
{
self.syntax = Syntax::new(self.text.slice(..), highlight_config, loader); self.syntax = Syntax::new(self.text.slice(..), highlight_config, loader);
} }
@ -1075,9 +1081,10 @@ impl Document {
pub fn set_language_by_language_id( pub fn set_language_by_language_id(
&mut self, &mut self,
language_id: &str, language_id: &str,
config_loader: Arc<syntax::Loader>, config_loader: Arc<ArcSwap<syntax::Loader>>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let language_config = config_loader let language_config = (*config_loader)
.load()
.language_config_for_language_id(language_id) .language_config_for_language_id(language_id)
.ok_or_else(|| anyhow!("invalid language id: {}", language_id))?; .ok_or_else(|| anyhow!("invalid language id: {}", language_id))?;
self.set_language(Some(language_config), Some(config_loader)); self.set_language(Some(language_config), Some(config_loader));

@ -23,7 +23,8 @@ use std::{
borrow::Cow, borrow::Cow,
cell::Cell, cell::Cell,
collections::{BTreeMap, HashMap}, collections::{BTreeMap, HashMap},
io::stdin, fs,
io::{self, stdin},
num::NonZeroUsize, num::NonZeroUsize,
path::{Path, PathBuf}, path::{Path, PathBuf},
pin::Pin, pin::Pin,
@ -45,10 +46,14 @@ use helix_core::{
}; };
use helix_dap as dap; use helix_dap as dap;
use helix_lsp::lsp; use helix_lsp::lsp;
use helix_stdx::path::canonicalize;
use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
use arc_swap::access::{DynAccess, DynGuard}; use arc_swap::{
access::{DynAccess, DynGuard},
ArcSwap,
};
fn deserialize_duration_millis<'de, D>(deserializer: D) -> Result<Duration, D::Error> fn deserialize_duration_millis<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where where
@ -916,7 +921,7 @@ pub struct Editor {
pub debugger_events: SelectAll<UnboundedReceiverStream<dap::Payload>>, pub debugger_events: SelectAll<UnboundedReceiverStream<dap::Payload>>,
pub breakpoints: HashMap<PathBuf, Vec<Breakpoint>>, pub breakpoints: HashMap<PathBuf, Vec<Breakpoint>>,
pub syn_loader: Arc<syntax::Loader>, pub syn_loader: Arc<ArcSwap<syntax::Loader>>,
pub theme_loader: Arc<theme::Loader>, pub theme_loader: Arc<theme::Loader>,
/// last_theme is used for theme previews. We store the current theme here, /// last_theme is used for theme previews. We store the current theme here,
/// and if previewing is cancelled, we can return to it. /// and if previewing is cancelled, we can return to it.
@ -1027,7 +1032,7 @@ impl Editor {
pub fn new( pub fn new(
mut area: Rect, mut area: Rect,
theme_loader: Arc<theme::Loader>, theme_loader: Arc<theme::Loader>,
syn_loader: Arc<syntax::Loader>, syn_loader: Arc<ArcSwap<syntax::Loader>>,
config: Arc<dyn DynAccess<Config>>, config: Arc<dyn DynAccess<Config>>,
handlers: Handlers, handlers: Handlers,
) -> Self { ) -> Self {
@ -1188,7 +1193,7 @@ impl Editor {
} }
let scopes = theme.scopes(); let scopes = theme.scopes();
self.syn_loader.set_scopes(scopes.to_vec()); (*self.syn_loader).load().set_scopes(scopes.to_vec());
match preview { match preview {
ThemeAction::Preview => { ThemeAction::Preview => {
@ -1215,6 +1220,90 @@ impl Editor {
self.launch_language_servers(doc_id) self.launch_language_servers(doc_id)
} }
/// moves/renames a path, invoking any event handlers (currently only lsp)
/// and calling `set_doc_path` if the file is open in the editor
pub fn move_path(&mut self, old_path: &Path, new_path: &Path) -> io::Result<()> {
let new_path = canonicalize(new_path);
// sanity check
if old_path == new_path {
return Ok(());
}
let is_dir = old_path.is_dir();
let language_servers: Vec<_> = self
.language_servers
.iter_clients()
.filter(|client| client.is_initialized())
.cloned()
.collect();
for language_server in language_servers {
let Some(request) = language_server.will_rename(old_path, &new_path, is_dir) else {
continue;
};
let edit = match helix_lsp::block_on(request) {
Ok(edit) => edit,
Err(err) => {
log::error!("invalid willRename response: {err:?}");
continue;
}
};
if let Err(err) = self.apply_workspace_edit(language_server.offset_encoding(), &edit) {
log::error!("failed to apply workspace edit: {err:?}")
}
}
fs::rename(old_path, &new_path)?;
if let Some(doc) = self.document_by_path(old_path) {
self.set_doc_path(doc.id(), &new_path);
}
let is_dir = new_path.is_dir();
for ls in self.language_servers.iter_clients() {
if let Some(notification) = ls.did_rename(old_path, &new_path, is_dir) {
tokio::spawn(notification);
};
}
self.language_servers
.file_event_handler
.file_changed(old_path.to_owned());
self.language_servers
.file_event_handler
.file_changed(new_path);
Ok(())
}
pub fn set_doc_path(&mut self, doc_id: DocumentId, path: &Path) {
let doc = doc_mut!(self, &doc_id);
let old_path = doc.path();
if let Some(old_path) = old_path {
// sanity check, should not occur but some callers (like an LSP) may
// create bogus calls
if old_path == path {
return;
}
// if we are open in LSPs send did_close notification
for language_server in doc.language_servers() {
tokio::spawn(language_server.text_document_did_close(doc.identifier()));
}
}
// we need to clear the list of language servers here so that
// refresh_doc_language/refresh_language_servers doesn't resend
// text_document_did_close. Since we called `text_document_did_close`
// we have fully unregistered this document from its LS
doc.language_servers.clear();
doc.set_path(Some(path));
self.refresh_doc_language(doc_id)
}
pub fn refresh_doc_language(&mut self, doc_id: DocumentId) {
let loader = self.syn_loader.clone();
let doc = doc_mut!(self, &doc_id);
doc.detect_language(loader);
doc.detect_indent_and_line_ending();
self.refresh_language_servers(doc_id);
let doc = doc_mut!(self, &doc_id);
let diagnostics = Editor::doc_diagnostics(&self.language_servers, &self.diagnostics, doc);
doc.replace_diagnostics(diagnostics, &[], None);
}
/// Launch a language server for a given document /// Launch a language server for a given document
fn launch_language_servers(&mut self, doc_id: DocumentId) { fn launch_language_servers(&mut self, doc_id: DocumentId) {
if !self.config().lsp.enable { if !self.config().lsp.enable {
@ -1257,7 +1346,7 @@ impl Editor {
.collect::<HashMap<_, _>>() .collect::<HashMap<_, _>>()
}); });
if language_servers.is_empty() { if language_servers.is_empty() && doc.language_servers.is_empty() {
return; return;
} }
@ -1904,10 +1993,12 @@ impl Editor {
if doc.restore_cursor { if doc.restore_cursor {
let text = doc.text().slice(..); let text = doc.text().slice(..);
let selection = doc.selection(view.id).clone().transform(|range| { let selection = doc.selection(view.id).clone().transform(|range| {
Range::new( let mut head = range.to();
range.from(), if range.head > range.anchor {
graphemes::prev_grapheme_boundary(text, range.to()), head = graphemes::prev_grapheme_boundary(text, head);
) }
Range::new(range.from(), head)
}); });
doc.set_selection(view.id, selection); doc.set_selection(view.id, selection);

@ -1,4 +1,8 @@
use crate::editor::Action;
use crate::Editor;
use crate::{DocumentId, ViewId}; use crate::{DocumentId, ViewId};
use helix_lsp::util::generate_transaction_from_edits;
use helix_lsp::{lsp, OffsetEncoding};
pub enum CompletionEvent { pub enum CompletionEvent {
/// Auto completion was triggered by typing a word char /// Auto completion was triggered by typing a word char
@ -39,3 +43,228 @@ pub enum SignatureHelpEvent {
Cancel, Cancel,
RequestComplete { open: bool }, RequestComplete { open: bool },
} }
#[derive(Debug)]
pub struct ApplyEditError {
pub kind: ApplyEditErrorKind,
pub failed_change_idx: usize,
}
#[derive(Debug)]
pub enum ApplyEditErrorKind {
DocumentChanged,
FileNotFound,
UnknownURISchema,
IoError(std::io::Error),
// TODO: check edits before applying and propagate failure
// InvalidEdit,
}
impl ToString for ApplyEditErrorKind {
fn to_string(&self) -> String {
match self {
ApplyEditErrorKind::DocumentChanged => "document has changed".to_string(),
ApplyEditErrorKind::FileNotFound => "file not found".to_string(),
ApplyEditErrorKind::UnknownURISchema => "URI schema not supported".to_string(),
ApplyEditErrorKind::IoError(err) => err.to_string(),
}
}
}
impl Editor {
fn apply_text_edits(
&mut self,
uri: &helix_lsp::Url,
version: Option<i32>,
text_edits: Vec<lsp::TextEdit>,
offset_encoding: OffsetEncoding,
) -> Result<(), ApplyEditErrorKind> {
let path = match uri.to_file_path() {
Ok(path) => path,
Err(_) => {
let err = format!("unable to convert URI to filepath: {}", uri);
log::error!("{}", err);
self.set_error(err);
return Err(ApplyEditErrorKind::UnknownURISchema);
}
};
let doc_id = match self.open(&path, Action::Load) {
Ok(doc_id) => doc_id,
Err(err) => {
let err = format!("failed to open document: {}: {}", uri, err);
log::error!("{}", err);
self.set_error(err);
return Err(ApplyEditErrorKind::FileNotFound);
}
};
let doc = doc_mut!(self, &doc_id);
if let Some(version) = version {
if version != doc.version() {
let err = format!("outdated workspace edit for {path:?}");
log::error!("{err}, expected {} but got {version}", doc.version());
self.set_error(err);
return Err(ApplyEditErrorKind::DocumentChanged);
}
}
// Need to determine a view for apply/append_changes_to_history
let view_id = self.get_synced_view_id(doc_id);
let doc = doc_mut!(self, &doc_id);
let transaction = generate_transaction_from_edits(doc.text(), text_edits, offset_encoding);
let view = view_mut!(self, view_id);
doc.apply(&transaction, view.id);
doc.append_changes_to_history(view);
Ok(())
}
// TODO make this transactional (and set failureMode to transactional)
pub fn apply_workspace_edit(
&mut self,
offset_encoding: OffsetEncoding,
workspace_edit: &lsp::WorkspaceEdit,
) -> Result<(), ApplyEditError> {
if let Some(ref document_changes) = workspace_edit.document_changes {
match document_changes {
lsp::DocumentChanges::Edits(document_edits) => {
for (i, document_edit) in document_edits.iter().enumerate() {
let edits = document_edit
.edits
.iter()
.map(|edit| match edit {
lsp::OneOf::Left(text_edit) => text_edit,
lsp::OneOf::Right(annotated_text_edit) => {
&annotated_text_edit.text_edit
}
})
.cloned()
.collect();
self.apply_text_edits(
&document_edit.text_document.uri,
document_edit.text_document.version,
edits,
offset_encoding,
)
.map_err(|kind| ApplyEditError {
kind,
failed_change_idx: i,
})?;
}
}
lsp::DocumentChanges::Operations(operations) => {
log::debug!("document changes - operations: {:?}", operations);
for (i, operation) in operations.iter().enumerate() {
match operation {
lsp::DocumentChangeOperation::Op(op) => {
self.apply_document_resource_op(op).map_err(|io| {
ApplyEditError {
kind: ApplyEditErrorKind::IoError(io),
failed_change_idx: i,
}
})?;
}
lsp::DocumentChangeOperation::Edit(document_edit) => {
let edits = document_edit
.edits
.iter()
.map(|edit| match edit {
lsp::OneOf::Left(text_edit) => text_edit,
lsp::OneOf::Right(annotated_text_edit) => {
&annotated_text_edit.text_edit
}
})
.cloned()
.collect();
self.apply_text_edits(
&document_edit.text_document.uri,
document_edit.text_document.version,
edits,
offset_encoding,
)
.map_err(|kind| {
ApplyEditError {
kind,
failed_change_idx: i,
}
})?;
}
}
}
}
}
return Ok(());
}
if let Some(ref changes) = workspace_edit.changes {
log::debug!("workspace changes: {:?}", changes);
for (i, (uri, text_edits)) in changes.iter().enumerate() {
let text_edits = text_edits.to_vec();
self.apply_text_edits(uri, None, text_edits, offset_encoding)
.map_err(|kind| ApplyEditError {
kind,
failed_change_idx: i,
})?;
}
}
Ok(())
}
fn apply_document_resource_op(&mut self, op: &lsp::ResourceOp) -> std::io::Result<()> {
use lsp::ResourceOp;
use std::fs;
match op {
ResourceOp::Create(op) => {
let path = op.uri.to_file_path().unwrap();
let ignore_if_exists = op.options.as_ref().map_or(false, |options| {
!options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false)
});
if !ignore_if_exists || !path.exists() {
// Create directory if it does not exist
if let Some(dir) = path.parent() {
if !dir.is_dir() {
fs::create_dir_all(dir)?;
}
}
fs::write(&path, [])?;
self.language_servers.file_event_handler.file_changed(path);
}
}
ResourceOp::Delete(op) => {
let path = op.uri.to_file_path().unwrap();
if path.is_dir() {
let recursive = op
.options
.as_ref()
.and_then(|options| options.recursive)
.unwrap_or(false);
if recursive {
fs::remove_dir_all(&path)?
} else {
fs::remove_dir(&path)?
}
self.language_servers.file_event_handler.file_changed(path);
} else if path.is_file() {
fs::remove_file(&path)?;
}
}
ResourceOp::Rename(op) => {
let from = op.old_uri.to_file_path().unwrap();
let to = op.new_uri.to_file_path().unwrap();
let ignore_if_exists = op.options.as_ref().map_or(false, |options| {
!options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false)
});
if !ignore_if_exists || !to.exists() {
self.move_path(&from, &to)?;
}
}
}
Ok(())
}
}

@ -253,7 +253,7 @@ source = { git = "https://github.com/FuelLabs/tree-sitter-sway", rev = "e491a005
name = "toml" name = "toml"
scope = "source.toml" scope = "source.toml"
injection-regex = "toml" injection-regex = "toml"
file-types = ["toml", "poetry.lock", "Cargo.lock"] file-types = ["toml", { glob = "poetry.lock" }, { glob = "Cargo.lock" }]
comment-token = "#" comment-token = "#"
language-servers = [ "taplo" ] language-servers = [ "taplo" ]
indent = { tab-width = 2, unit = " " } indent = { tab-width = 2, unit = " " }
@ -292,7 +292,7 @@ source = { git = "https://github.com/yusdacra/tree-sitter-protobuf", rev = "19c2
name = "elixir" name = "elixir"
scope = "source.elixir" scope = "source.elixir"
injection-regex = "(elixir|ex)" injection-regex = "(elixir|ex)"
file-types = ["ex", "exs", "mix.lock"] file-types = ["ex", "exs", { glob = "mix.lock" }]
shebangs = ["elixir"] shebangs = ["elixir"]
roots = ["mix.exs", "mix.lock"] roots = ["mix.exs", "mix.lock"]
comment-token = "#" comment-token = "#"
@ -311,6 +311,8 @@ file-types = ["fish"]
shebangs = ["fish"] shebangs = ["fish"]
comment-token = "#" comment-token = "#"
indent = { tab-width = 4, unit = " " } indent = { tab-width = 4, unit = " " }
auto-format = true
formatter = { command = "fish_indent" }
[[grammar]] [[grammar]]
name = "fish" name = "fish"
@ -326,6 +328,29 @@ comment-token = "//"
language-servers = [ "mint" ] language-servers = [ "mint" ]
indent = { tab-width = 2, unit = " " } indent = { tab-width = 2, unit = " " }
[[language]]
name = "janet"
scope = "source.janet"
injection-regex = "janet"
file-types = ["cgen", "janet", "jdn"]
shebangs = ["janet"]
roots = ["project.janet"]
comment-token = "#"
indent = { tab-width = 2, unit = " " }
formatter = { command = "janet-format" }
grammar = "janet-simple"
[language.auto-pairs]
'"' = '"'
'(' = ')'
'[' = ']'
'{' = '}'
"`" = "`"
[[grammar]]
name = "janet-simple"
source = { git = "https://github.com/sogaiu/tree-sitter-janet-simple", rev = "51271e260346878e1a1aa6c506ce6a797b7c25e2" }
[[language]] [[language]]
name = "json" name = "json"
scope = "source.json" scope = "source.json"
@ -338,20 +363,20 @@ file-types = [
"geojson", "geojson",
"gltf", "gltf",
"webmanifest", "webmanifest",
"flake.lock", { glob = "flake.lock" },
".babelrc", { glob = ".babelrc" },
".bowerrc", { glob = ".bowerrc" },
".jscrc", { glob = ".jscrc" },
"js.map", "js.map",
"ts.map", "ts.map",
"css.map", "css.map",
".jslintrc", { glob = ".jslintrc" },
"jsonld", "jsonld",
".vuerc", { glob = ".vuerc" },
"composer.lock", { glob = "composer.lock" },
".watchmanconfig", { glob = ".watchmanconfig" },
"avsc", "avsc",
".prettierrc" { glob = ".prettierrc" },
] ]
language-servers = [ "vscode-json-language-server" ] language-servers = [ "vscode-json-language-server" ]
auto-format = true auto-format = true
@ -416,7 +441,7 @@ source = { git = "https://github.com/tree-sitter/tree-sitter-c", rev = "7175a6dd
name = "cpp" name = "cpp"
scope = "source.cpp" scope = "source.cpp"
injection-regex = "cpp" injection-regex = "cpp"
file-types = ["cc", "hh", "c++", "cpp", "hpp", "h", "ipp", "tpp", "cxx", "hxx", "ixx", "txx", "ino", "C", "H", "cu", "cuh", "cppm", "h++", "ii", "inl", { suffix = ".hpp.in" }, { suffix = ".h.in" }] file-types = ["cc", "hh", "c++", "cpp", "hpp", "h", "ipp", "tpp", "cxx", "hxx", "ixx", "txx", "ino", "C", "H", "cu", "cuh", "cppm", "h++", "ii", "inl", { glob = ".hpp.in" }, { glob = ".h.in" }]
comment-token = "//" comment-token = "//"
language-servers = [ "clangd" ] language-servers = [ "clangd" ]
indent = { tab-width = 2, unit = " " } indent = { tab-width = 2, unit = " " }
@ -491,6 +516,30 @@ args = { processId = "{0}" }
name = "c-sharp" name = "c-sharp"
source = { git = "https://github.com/tree-sitter/tree-sitter-c-sharp", rev = "5b60f99545fea00a33bbfae5be956f684c4c69e2" } source = { git = "https://github.com/tree-sitter/tree-sitter-c-sharp", rev = "5b60f99545fea00a33bbfae5be956f684c4c69e2" }
[[language]]
name = "cel"
scope = "source.cel"
injection-regex = "cel"
file-types = ["cel"]
comment-token = "//"
indent = { tab-width = 2, unit = " " }
[[grammar]]
name = "cel"
source = { git = "https://github.com/bufbuild/tree-sitter-cel", rev = "9f2b65da14c216df53933748e489db0f11121464" }
[[language]]
name = "spicedb"
scope = "source.zed"
injection-regex = "spicedb"
file-types = ["zed"]
comment-token = "//"
indent = { tab-width = 2, unit = " " }
[[grammar]]
name = "spicedb"
source = { git = "https://github.com/jzelinskie/tree-sitter-spicedb", rev = "a4e4645651f86d6684c15dfa9931b7841dc52a66" }
[[language]] [[language]]
name = "go" name = "go"
scope = "source.go" scope = "source.go"
@ -548,7 +597,7 @@ source = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "64457ea
name = "gomod" name = "gomod"
scope = "source.gomod" scope = "source.gomod"
injection-regex = "gomod" injection-regex = "gomod"
file-types = ["go.mod"] file-types = [{ glob = "go.mod" }]
auto-format = true auto-format = true
comment-token = "//" comment-token = "//"
language-servers = [ "gopls" ] language-servers = [ "gopls" ]
@ -575,7 +624,7 @@ source = { git = "https://github.com/dannylongeuay/tree-sitter-go-template", rev
name = "gowork" name = "gowork"
scope = "source.gowork" scope = "source.gowork"
injection-regex = "gowork" injection-regex = "gowork"
file-types = ["go.work"] file-types = [{ glob = "go.work" }]
auto-format = true auto-format = true
comment-token = "//" comment-token = "//"
language-servers = [ "gopls" ] language-servers = [ "gopls" ]
@ -590,7 +639,7 @@ name = "javascript"
scope = "source.js" scope = "source.js"
injection-regex = "(js|javascript)" injection-regex = "(js|javascript)"
language-id = "javascript" language-id = "javascript"
file-types = ["js", "mjs", "cjs", "rules", "es6", "pac", "jakefile"] file-types = ["js", "mjs", "cjs", "rules", "es6", "pac", { glob = "jakefile" }]
shebangs = ["node"] shebangs = ["node"]
comment-token = "//" comment-token = "//"
language-servers = [ "typescript-language-server" ] language-servers = [ "typescript-language-server" ]
@ -693,7 +742,7 @@ source = { git = "https://github.com/tree-sitter/tree-sitter-html", rev = "29f53
name = "python" name = "python"
scope = "source.python" scope = "source.python"
injection-regex = "python" injection-regex = "python"
file-types = ["py","pyi","py3","pyw","ptl",".pythonstartup",".pythonrc","SConstruct", "rpy", "cpy", "ipy", "pyt", "SConscript"] file-types = ["py", "pyi", "py3", "pyw", "ptl", "rpy", "cpy", "ipy", "pyt", { glob = ".pythonstartup" }, { glob = ".pythonrc" }, { glob = "SConstruct" }, { glob = "SConscript" }]
shebangs = ["python"] shebangs = ["python"]
roots = ["pyproject.toml", "setup.py", "poetry.lock", "pyrightconfig.json"] roots = ["pyproject.toml", "setup.py", "poetry.lock", "pyrightconfig.json"]
comment-token = "#" comment-token = "#"
@ -746,38 +795,38 @@ injection-regex = "ruby"
file-types = [ file-types = [
"rb", "rb",
"rake", "rake",
"rakefile",
"irb", "irb",
"gemfile",
"gemspec", "gemspec",
"Rakefile",
"Gemfile",
"rabl", "rabl",
"jbuilder", "jbuilder",
"jb", "jb",
"Podfile",
"podspec", "podspec",
"Vagrantfile",
"Brewfile",
"rjs", "rjs",
"rbi", "rbi",
"Guardfile", { glob = "rakefile" },
"Capfile", { glob = "gemfile" },
"Cheffile", { glob = "Rakefile" },
"Hobofile", { glob = "Gemfile" },
"Appraisals", { glob = "Podfile" },
"Rantfile", { glob = "Vagrantfile" },
"Berksfile", { glob = "Brewfile" },
"Berksfile.lock", { glob = "Guardfile" },
"Thorfile", { glob = "Capfile" },
"Puppetfile", { glob = "Cheffile" },
"Fastfile", { glob = "Hobofile" },
"Appfile", { glob = "Appraisals" },
"Deliverfile", { glob = "Rantfile" },
"Matchfile", { glob = "Berksfile" },
"Scanfile", { glob = "Berksfile.lock" },
"Snapfile", { glob = "Thorfile" },
"Gymfile" { glob = "Puppetfile" },
{ glob = "Fastfile" },
{ glob = "Appfile" },
{ glob = "Deliverfile" },
{ glob = "Matchfile" },
{ glob = "Scanfile" },
{ glob = "Snapfile" },
{ glob = "Gymfile" },
] ]
shebangs = ["ruby"] shebangs = ["ruby"]
comment-token = "#" comment-token = "#"
@ -796,43 +845,43 @@ file-types = [
"sh", "sh",
"bash", "bash",
"zsh", "zsh",
".bash_history",
".bash_login",
".bash_logout",
".bash_profile",
".bashrc",
".profile",
".zshenv",
"zshenv", "zshenv",
".zlogin",
"zlogin", "zlogin",
".zlogout",
"zlogout", "zlogout",
".zprofile",
"zprofile", "zprofile",
".zshrc",
"zshrc", "zshrc",
".zimrc",
"APKBUILD",
"PKGBUILD",
"eclass", "eclass",
"ebuild", "ebuild",
"bazelrc", "bazelrc",
".bash_aliases",
"Renviron", "Renviron",
".Renviron",
".xprofile",
".xsession",
".xsessionrc",
"zsh-theme", "zsh-theme",
"ksh", "ksh",
"cshrc", "cshrc",
"tcshrc", "tcshrc",
".yashrc",
".yash_profile",
".hushlogin",
"bashrc_Apple_Terminal", "bashrc_Apple_Terminal",
"zshrc_Apple_Terminal" "zshrc_Apple_Terminal",
{ glob = ".bash_history" },
{ glob = ".bash_login" },
{ glob = ".bash_logout" },
{ glob = ".bash_profile" },
{ glob = ".bashrc" },
{ glob = ".profile" },
{ glob = ".zshenv" },
{ glob = ".zlogin" },
{ glob = ".zlogout" },
{ glob = ".zprofile" },
{ glob = ".zshrc" },
{ glob = ".zimrc" },
{ glob = "APKBUILD" },
{ glob = "PKGBUILD" },
{ glob = ".bash_aliases" },
{ glob = ".Renviron" },
{ glob = ".xprofile" },
{ glob = ".xsession" },
{ glob = ".xsessionrc" },
{ glob = ".yashrc" },
{ glob = ".yash_profile" },
{ glob = ".hushlogin" },
] ]
shebangs = ["sh", "bash", "dash", "zsh"] shebangs = ["sh", "bash", "dash", "zsh"]
comment-token = "#" comment-token = "#"
@ -1135,7 +1184,7 @@ source = { git = "https://github.com/postsolar/tree-sitter-purescript", rev = "5
name = "zig" name = "zig"
scope = "source.zig" scope = "source.zig"
injection-regex = "zig" injection-regex = "zig"
file-types = ["zig"] file-types = ["zig", "zon"]
roots = ["build.zig"] roots = ["build.zig"]
auto-format = true auto-format = true
comment-token = "//" comment-token = "//"
@ -1193,7 +1242,7 @@ source = { git = "https://github.com/the-mikedavis/tree-sitter-tsq", rev = "48b5
[[language]] [[language]]
name = "cmake" name = "cmake"
scope = "source.cmake" scope = "source.cmake"
file-types = ["cmake", "CMakeLists.txt"] file-types = ["cmake", { glob = "CMakeLists.txt" }]
comment-token = "#" comment-token = "#"
indent = { tab-width = 2, unit = " " } indent = { tab-width = 2, unit = " " }
language-servers = [ "cmake-language-server" ] language-servers = [ "cmake-language-server" ]
@ -1206,7 +1255,7 @@ source = { git = "https://github.com/uyha/tree-sitter-cmake", rev = "6e51463ef30
[[language]] [[language]]
name = "make" name = "make"
scope = "source.make" scope = "source.make"
file-types = ["Makefile", "makefile", "make", "mk", "mak", "GNUmakefile", "OCamlMakefile"] file-types = [{ glob = "Makefile" }, { glob = "makefile" }, "make", "mk", "mak", {glob = "GNUmakefile" }, { glob = "OCamlMakefile" }]
shebangs = ["make", "gmake"] shebangs = ["make", "gmake"]
injection-regex = "(make|makefile|Makefile|mk)" injection-regex = "(make|makefile|Makefile|mk)"
comment-token = "#" comment-token = "#"
@ -1349,7 +1398,7 @@ source = { git = "https://github.com/Flakebi/tree-sitter-tablegen", rev = "568dd
name = "markdown" name = "markdown"
scope = "source.md" scope = "source.md"
injection-regex = "md|markdown" injection-regex = "md|markdown"
file-types = ["md", "markdown", "PULLREQ_EDITMSG", "mkd", "mdwn", "mdown", "markdn", "mdtxt", "mdtext", "workbook"] file-types = ["md", "markdown", "mkd", "mdwn", "mdown", "markdn", "mdtxt", "mdtext", "workbook", { glob = "PULLREQ_EDITMSG" }]
roots = [".marksman.toml"] roots = [".marksman.toml"]
language-servers = [ "marksman" ] language-servers = [ "marksman" ]
indent = { tab-width = 2, unit = " " } indent = { tab-width = 2, unit = " " }
@ -1401,7 +1450,7 @@ name = "dockerfile"
scope = "source.dockerfile" scope = "source.dockerfile"
injection-regex = "docker|dockerfile" injection-regex = "docker|dockerfile"
roots = ["Dockerfile", "Containerfile"] roots = ["Dockerfile", "Containerfile"]
file-types = ["Dockerfile", "dockerfile", "Containerfile", "containerfile"] file-types = [{ glob = "Dockerfile*" }, { glob = "dockerfile*" }, { glob = "Containerfile*" }, { glob = "containerfile*" }]
comment-token = "#" comment-token = "#"
indent = { tab-width = 2, unit = " " } indent = { tab-width = 2, unit = " " }
language-servers = [ "docker-langserver" ] language-servers = [ "docker-langserver" ]
@ -1413,7 +1462,7 @@ source = { git = "https://github.com/camdencheek/tree-sitter-dockerfile", rev =
[[language]] [[language]]
name = "git-commit" name = "git-commit"
scope = "git.commitmsg" scope = "git.commitmsg"
file-types = ["COMMIT_EDITMSG"] file-types = [{ glob = "COMMIT_EDITMSG" }]
comment-token = "#" comment-token = "#"
indent = { tab-width = 2, unit = " " } indent = { tab-width = 2, unit = " " }
rulers = [51, 73] rulers = [51, 73]
@ -1438,7 +1487,7 @@ source = { git = "https://github.com/the-mikedavis/tree-sitter-diff", rev = "fd7
[[language]] [[language]]
name = "git-rebase" name = "git-rebase"
scope = "source.gitrebase" scope = "source.gitrebase"
file-types = ["git-rebase-todo"] file-types = [{ glob = "git-rebase-todo" }]
injection-regex = "git-rebase" injection-regex = "git-rebase"
comment-token = "#" comment-token = "#"
indent = { tab-width = 2, unit = "y" } indent = { tab-width = 2, unit = "y" }
@ -1451,7 +1500,7 @@ source = { git = "https://github.com/the-mikedavis/tree-sitter-git-rebase", rev
name = "regex" name = "regex"
scope = "source.regex" scope = "source.regex"
injection-regex = "regex" injection-regex = "regex"
file-types = ["regex", ".Rbuildignore"] file-types = ["regex", { glob = ".Rbuildignore" }]
[[grammar]] [[grammar]]
name = "regex" name = "regex"
@ -1460,7 +1509,7 @@ source = { git = "https://github.com/tree-sitter/tree-sitter-regex", rev = "e1cf
[[language]] [[language]]
name = "git-config" name = "git-config"
scope = "source.gitconfig" scope = "source.gitconfig"
file-types = [".gitmodules", ".gitconfig", { suffix = ".git/config" }, { suffix = ".config/git/config" }] file-types = [{ glob = ".gitmodules" }, { glob = ".gitconfig" }, { glob = ".git/config" }, { glob = ".config/git/config" }]
injection-regex = "git-config" injection-regex = "git-config"
comment-token = "#" comment-token = "#"
indent = { tab-width = 4, unit = "\t" } indent = { tab-width = 4, unit = "\t" }
@ -1472,7 +1521,7 @@ source = { git = "https://github.com/the-mikedavis/tree-sitter-git-config", rev
[[language]] [[language]]
name = "git-attributes" name = "git-attributes"
scope = "source.gitattributes" scope = "source.gitattributes"
file-types = [".gitattributes"] file-types = [{ glob = ".gitattributes" }]
injection-regex = "git-attributes" injection-regex = "git-attributes"
comment-token = "#" comment-token = "#"
grammar = "gitattributes" grammar = "gitattributes"
@ -1484,7 +1533,7 @@ source = { git = "https://github.com/mtoohey31/tree-sitter-gitattributes", rev =
[[language]] [[language]]
name = "git-ignore" name = "git-ignore"
scope = "source.gitignore" scope = "source.gitignore"
file-types = [".gitignore", ".gitignore_global", ".ignore", ".prettierignore", ".eslintignore", ".npmignore", "CODEOWNERS", { suffix = ".config/helix/ignore" }, { suffix = ".helix/ignore" }] file-types = [{ glob = ".gitignore" }, { glob = ".gitignore_global" }, { glob = ".ignore" }, { glob = ".prettierignore" }, { glob = ".eslintignore" }, { glob = ".npmignore"}, { glob = "CODEOWNERS" }, { glob = ".config/helix/ignore" }, { glob = ".helix/ignore" }]
injection-regex = "git-ignore" injection-regex = "git-ignore"
comment-token = "#" comment-token = "#"
grammar = "gitignore" grammar = "gitignore"
@ -1549,7 +1598,7 @@ source = { git = "https://github.com/jaredramirez/tree-sitter-rescript", rev = "
name = "erlang" name = "erlang"
scope = "source.erlang" scope = "source.erlang"
injection-regex = "erl(ang)?" injection-regex = "erl(ang)?"
file-types = ["erl", "hrl", "app", "rebar.config", "rebar.lock"] file-types = ["erl", "hrl", "app", { glob = "rebar.config" }, { glob = "rebar.lock" }]
roots = ["rebar.config"] roots = ["rebar.config"]
shebangs = ["escript"] shebangs = ["escript"]
comment-token = "%%" comment-token = "%%"
@ -1675,7 +1724,7 @@ source = { git = "https://github.com/Hubro/tree-sitter-robot", rev = "322e4cc657
name = "r" name = "r"
scope = "source.r" scope = "source.r"
injection-regex = "(r|R)" injection-regex = "(r|R)"
file-types = ["r", "R", ".Rprofile", "Rprofile.site", ".RHistory"] file-types = ["r", "R", { glob = ".Rprofile" }, { glob = "Rprofile.site" }, { glob = ".RHistory" }]
shebangs = ["r", "R"] shebangs = ["r", "R"]
comment-token = "#" comment-token = "#"
indent = { tab-width = 2, unit = " " } indent = { tab-width = 2, unit = " " }
@ -1707,7 +1756,7 @@ language-servers = [ "sourcekit-lsp" ]
[[grammar]] [[grammar]]
name = "swift" name = "swift"
source = { git = "https://github.com/alex-pinkus/tree-sitter-swift", rev = "77c6312c8438f4dbaa0350cec92b3d6dd3d74a66" } source = { git = "https://github.com/alex-pinkus/tree-sitter-swift", rev = "b1b66955d420d5cf5ff268ae552f0d6e43ff66e1" }
[[language]] [[language]]
name = "erb" name = "erb"
@ -1809,7 +1858,7 @@ language-servers = [ "nu-lsp" ]
[[grammar]] [[grammar]]
name = "nu" name = "nu"
source = { git = "https://github.com/nushell/tree-sitter-nu", rev = "98c11c491e3405c75affa1cf004097692da3dda2" } source = { git = "https://github.com/nushell/tree-sitter-nu", rev = "358c4f509eb97f0148bbd25ad36acc729819b9c1" }
[[language]] [[language]]
name = "vala" name = "vala"
@ -1874,13 +1923,14 @@ source = { git = "https://github.com/fvacek/tree-sitter-cpon", rev = "0d01fcdae5
[[language]] [[language]]
name = "odin" name = "odin"
auto-format = false auto-format = true
scope = "source.odin" scope = "source.odin"
file-types = ["odin"] file-types = ["odin"]
roots = ["ols.json"] roots = ["ols.json"]
language-servers = [ "ols" ] language-servers = [ "ols" ]
comment-token = "//" comment-token = "//"
indent = { tab-width = 4, unit = "\t" } indent = { tab-width = 4, unit = "\t" }
formatter = { command = "odinfmt", args = [ "-stdin", "true" ] }
[[grammar]] [[grammar]]
name = "odin" name = "odin"
@ -1890,7 +1940,7 @@ source = { git = "https://github.com/ap29600/tree-sitter-odin", rev = "b219207e4
name = "meson" name = "meson"
scope = "source.meson" scope = "source.meson"
injection-regex = "meson" injection-regex = "meson"
file-types = ["meson.build", "meson_options.txt"] file-types = [{ glob = "meson.build" }, { glob = "meson_options.txt" }]
comment-token = "#" comment-token = "#"
indent = { tab-width = 2, unit = " " } indent = { tab-width = 2, unit = " " }
@ -1901,7 +1951,7 @@ source = { git = "https://github.com/staysail/tree-sitter-meson", rev = "32a83e8
[[language]] [[language]]
name = "sshclientconfig" name = "sshclientconfig"
scope = "source.sshclientconfig" scope = "source.sshclientconfig"
file-types = [{ suffix = ".ssh/config" }, { suffix = "/etc/ssh/ssh_config" }] file-types = [{ glob = ".ssh/config" }, { glob = "/etc/ssh/ssh_config" }]
comment-token = "#" comment-token = "#"
[[grammar]] [[grammar]]
@ -2022,7 +2072,7 @@ source = { git = "https://github.com/sogaiu/tree-sitter-clojure", rev = "e57c569
name = "starlark" name = "starlark"
scope = "source.starlark" scope = "source.starlark"
injection-regex = "(starlark|bzl|bazel)" injection-regex = "(starlark|bzl|bazel)"
file-types = ["bzl", "bazel", "BUILD", "star"] file-types = ["bzl", "bazel", "star", { glob = "BUILD" }, { glob = "BUILD.*" }]
comment-token = "#" comment-token = "#"
indent = { tab-width = 4, unit = " " } indent = { tab-width = 4, unit = " " }
grammar = "python" grammar = "python"
@ -2116,7 +2166,7 @@ language-servers = [ "slint-lsp" ]
[[grammar]] [[grammar]]
name = "slint" name = "slint"
source = { git = "https://github.com/jrmoulton/tree-sitter-slint", rev = "00c8a2d3645766f68c0d0460086c0a994e5b0d85" } source = { git = "https://github.com/slint-ui/tree-sitter-slint", rev = "15618215b79b9db08f824a5c97a12d073dcc1c00" }
[[language]] [[language]]
name = "task" name = "task"
@ -2390,7 +2440,7 @@ source = { git = "https://github.com/hh9527/tree-sitter-wit", rev = "c917790ab9a
[[language]] [[language]]
name = "env" name = "env"
scope = "source.env" scope = "source.env"
file-types = [".env", ".env.local", ".env.development", ".env.production", ".env.dist", ".envrc", ".envrc.local", ".envrc.private"] file-types = [{ glob = ".env" }, { glob = ".env.*" }, { glob = ".envrc" }, { glob = ".envrc.*" }]
injection-regex = "env" injection-regex = "env"
comment-token = "#" comment-token = "#"
indent = { tab-width = 4, unit = "\t" } indent = { tab-width = 4, unit = "\t" }
@ -2418,7 +2468,7 @@ file-types = [
"volume", "volume",
"kube", "kube",
"network", "network",
".editorconfig", { glob = ".editorconfig" },
"properties", "properties",
"cfg", "cfg",
"directory" "directory"
@ -2546,7 +2596,7 @@ source = { git = "https://github.com/mtoohey31/tree-sitter-pem", rev = "be67a433
[[language]] [[language]]
name = "passwd" name = "passwd"
scope = "source.passwd" scope = "source.passwd"
file-types = ["passwd"] file-types = [{ glob = "passwd" }]
[[grammar]] [[grammar]]
name = "passwd" name = "passwd"
@ -2555,7 +2605,7 @@ source = { git = "https://github.com/ath3/tree-sitter-passwd", rev = "20239395ea
[[language]] [[language]]
name = "hosts" name = "hosts"
scope = "source.hosts" scope = "source.hosts"
file-types = ["hosts"] file-types = [{ glob = "hosts" }]
comment-token = "#" comment-token = "#"
[[grammar]] [[grammar]]
@ -2763,10 +2813,12 @@ source = { git = "https://github.com/lefp/tree-sitter-opencl", rev = "8e1d24a570
[[language]] [[language]]
name = "just" name = "just"
scope = "source.just" scope = "source.just"
file-types = ["justfile", "Justfile", ".justfile", ".Justfile"] file-types = [{ glob = "justfile" }, { glob = "Justfile" }, { glob = ".justfile" }, { glob = ".Justfile" }]
injection-regex = "just" injection-regex = "just"
comment-token = "#" comment-token = "#"
indent = { tab-width = 4, unit = "\t" } indent = { tab-width = 4, unit = "\t" }
auto-format = true
formatter = { command = "just", args = ["--dump"] }
[[grammar]] [[grammar]]
name = "just" name = "just"
@ -2917,12 +2969,12 @@ indent = { tab-width = 4, unit = " " }
[[grammar]] [[grammar]]
name = "unison" name = "unison"
source = { git = "https://github.com/kylegoetz/tree-sitter-unison", rev = "aaec316774c8b50d367ec7cf26523aac5ef0cfc5" } source = { git = "https://github.com/kylegoetz/tree-sitter-unison", rev = "1f505e2447fa876a87aee47ff3d70b9e141c744f" }
[[language]] [[language]]
name = "todotxt" name = "todotxt"
scope = "text.todotxt" scope = "text.todotxt"
file-types = [{ suffix = ".todo.txt" }, "todotxt"] file-types = [{ glob = "todo.txt" }, { glob = "*.todo.txt" }, "todotxt"]
formatter = { command = "sort" } formatter = { command = "sort" }
auto-format = true auto-format = true
@ -3009,13 +3061,16 @@ name = "log"
source = { git = "https://github.com/Tudyx/tree-sitter-log", rev = "62cfe307e942af3417171243b599cc7deac5eab9" } source = { git = "https://github.com/Tudyx/tree-sitter-log", rev = "62cfe307e942af3417171243b599cc7deac5eab9" }
[[language]] [[language]]
name = "janet" name = "hoon"
scope = "source.janet" scope = "source.hoon"
injection-regex = "janet" injection-regex = "hoon"
file-types = ["janet"] file-types = ["hoon"]
comment-token = "#" comment-token = "::"
indent = { tab-width = 2, unit = " " } indent = {tab-width = 2, unit = " "}
grammar = "clojure"
[[grammar]]
name = "hoon"
source = { git = "https://github.com/urbit-pilled/tree-sitter-hoon", rev = "1d5df35af3e0afe592832a67b9fb3feeeba1f7b6" }
[[language]] [[language]]
name = "hocon" name = "hocon"
@ -3028,3 +3083,33 @@ indent = { tab-width = 2, unit = " " }
[[grammar]] [[grammar]]
name = "hocon" name = "hocon"
source = { git = "https://github.com/antosha417/tree-sitter-hocon", rev = "c390f10519ae69fdb03b3e5764f5592fb6924bcc" } source = { git = "https://github.com/antosha417/tree-sitter-hocon", rev = "c390f10519ae69fdb03b3e5764f5592fb6924bcc" }
[[language]]
name = "tact"
scope = "source.tact"
injection-regex = "tact"
file-types = ["tact"]
comment-token = "//"
indent = { tab-width = 4, unit = " " }
[language.auto-pairs]
'"' = '"'
'{' = '}'
'(' = ')'
'<' = '>'
[[grammar]]
name = "tact"
source = { git = "https://github.com/tact-lang/tree-sitter-tact", rev = "ec57ab29c86d632639726631fb2bb178d23e1c91" }
[[language]]
name = "pkl"
scope = "source.pkl"
injection-regex = "pkl"
file-types = ["pkl", "pcf"]
comment-token = "//"
indent = { tab-width = 2, unit = " " }
[[grammar]]
name = "pkl"
source = { git = "https://github.com/apple/tree-sitter-pkl", rev = "c03f04a313b712f8ab00a2d862c10b37318699ae" }

@ -0,0 +1,66 @@
; Operators
[
"-"
"!"
"*"
"/"
"&&"
"%"
"+"
"<"
"<="
"=="
">"
">="
"||"
] @operator
; Keywords
[
"in"
] @keyword
; Function calls
(call_expression
function: (identifier) @function)
(member_call_expression
function: (identifier) @function)
; Identifiers
(select_expression
operand: (identifier) @type)
(select_expression
operand: (select_expression
member: (identifier) @type))
(identifier) @variable.other.member
; Literals
[
(double_quote_string_literal)
(single_quoted_string_literal)
(triple_double_quote_string_literal)
(triple_single_quoted_string_literal)
] @string
[
(int_literal)
(uint_literal)
] @constant.numeric.integer
(float_literal) @constant.numeric.float
[
(true)
(false)
] @constant.builtin.boolean
(null) @constant.builtin
(comment) @comment

@ -13,5 +13,7 @@
(typed_default_parameter) (typed_default_parameter)
] @parameter.inside @parameter.around) ] @parameter.inside @parameter.around)
(arguments (_expression) @parameter.inside @parameter.around)
(comment) @comment.inside (comment) @comment.inside
(comment)+ @comment.around (comment)+ @comment.around

@ -183,9 +183,12 @@
[ [
(int_literal) (int_literal)
] @constant.numeric.integer
[
(float_literal) (float_literal)
(imaginary_literal) (imaginary_literal)
] @constant.numeric.integer ] @constant.numeric.float
[ [
(true) (true)
@ -197,4 +200,31 @@
(iota) (iota)
] @constant.builtin ] @constant.builtin
; Comments
(comment) @comment (comment) @comment
; Doc Comments
(source_file
.
(comment)+ @comment.block.documentation)
(source_file
(comment)+ @comment.block.documentation
.
(const_declaration))
(source_file
(comment)+ @comment.block.documentation
.
(function_declaration))
(source_file
(comment)+ @comment.block.documentation
.
(type_declaration))
(source_file
(comment)+ @comment.block.documentation
.
(var_declaration))

@ -1,2 +1,14 @@
((comment) @injection.content ((comment) @injection.content
(#set! injection.language "comment")) (#set! injection.language "comment"))
(call_expression
(selector_expression) @_function
(#any-of? @_function "regexp.Match" "regexp.MatchReader" "regexp.MatchString" "regexp.Compile" "regexp.CompilePOSIX" "regexp.MustCompile" "regexp.MustCompilePOSIX")
(argument_list
.
[
(raw_string_literal)
(interpreted_string_literal)
] @injection.content
(#set! injection.language "regex")))

@ -0,0 +1,32 @@
(number) @constant.numeric
(string) @string
[
"("
")"
"["
"]"
] @punctuation.bracket
[
(coreTerminator)
(seriesTerminator)
] @punctuation.delimiter
(rune) @keyword
(term) @constant
(aura) @constant.builtin
(Gap) @comment
(boolean) @constant.builtin
(date) @constant.builtin
(mold) @constant.builtin
(specialIndex) @constant.builtin
(lark) @operator
(fullContext) @constant.builtin

File diff suppressed because one or more lines are too long

@ -2,7 +2,6 @@
;;; keywords ;;; keywords
[ [
"def" "def"
"def-env"
"alias" "alias"
"export-env" "export-env"
"export" "export"
@ -73,7 +72,6 @@
"tb" "tB" "Tb" "TB" "tb" "tB" "Tb" "TB"
"pb" "pB" "Pb" "PB" "pb" "pB" "Pb" "PB"
"eb" "eB" "Eb" "EB" "eb" "eB" "Eb" "EB"
"zb" "zB" "Zb" "ZB"
"kib" "kiB" "kIB" "kIb" "Kib" "KIb" "KIB" "kib" "kiB" "kIB" "kIb" "Kib" "KIb" "KIB"
"mib" "miB" "mIB" "mIb" "Mib" "MIb" "MIB" "mib" "miB" "mIB" "mIb" "Mib" "MIb" "MIB"
@ -81,7 +79,6 @@
"tib" "tiB" "tIB" "tIb" "Tib" "TIb" "TIB" "tib" "tiB" "tIB" "tIb" "Tib" "TIb" "TIB"
"pib" "piB" "pIB" "pIb" "Pib" "PIb" "PIB" "pib" "piB" "pIB" "pIb" "Pib" "PIb" "PIB"
"eib" "eiB" "eIB" "eIb" "Eib" "EIb" "EIB" "eib" "eiB" "eIB" "eIb" "Eib" "EIb" "EIB"
"zib" "ziB" "zIB" "zIb" "Zib" "ZIb" "ZIB"
] @variable.parameter ] @variable.parameter
) )
(val_binary (val_binary

@ -0,0 +1,179 @@
; Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
;
; Licensed under the Apache License, Version 2.0 (the "License");
; you may not use this file except in compliance with the License.
; You may obtain a copy of the License at
;
; https://www.apache.org/licenses/LICENSE-2.0
;
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS,
; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
; See the License for the specific language governing permissions and
; limitations under the License.
; this definition is imprecise in that
; * any qualified or unqualified call to a method named "Regex" is considered a regex
; * string delimiters are considered part of the regex
; Operators
[
"??"
"@"
"="
"<"
">"
"!"
"=="
"!="
"<="
">="
"&&"
"||"
"+"
"-"
"**"
"*"
"/"
"~/"
"%"
"|>"
] @keyword.operator
[
"?"
"|"
"->"
] @operator.type
[
","
":"
"."
"?."
] @punctuation.delimiter
[
"("
")"
"]"
"{"
"}"
; "[" @punctuation.bracket TODO: FIGURE OUT HOW TO REFER TO CUSTOM TOKENS
] @punctuation.bracket
; Keywords
[
"abstract"
"amends"
"as"
"class"
"extends"
"external"
"function"
"hidden"
"import"
"import*"
"in"
"let"
"local"
"module"
"new"
"open"
"out"
"typealias"
"when"
] @keyword
[
"if"
"is"
"else"
] @keyword.control.conditional
[
"for"
] @keyword.control.repeat
(importExpr "import" @keyword.control.import)
(importGlobExpr "import*" @keyword.control.import)
"read" @function.builtin
"read?" @function.builtin
"read*" @function.builtin
"throw" @function.builtin
"trace" @function.builtin
(moduleExpr "module" @type.builtin)
"nothing" @type.builtin
"unknown" @type.builtin
(outerExpr) @variable.builtin
"super" @variable.builtin
(thisExpr) @variable.builtin
[
(falseLiteral)
(nullLiteral)
(trueLiteral)
] @constant.builtin
; Literals
(stringConstant) @string
(slStringLiteral) @string
(mlStringLiteral) @string
(escapeSequence) @constent.character.escape
(intLiteral) @constant.numeric.integer
(floatLiteral) @constant.numeric.float
(interpolationExpr
"\\(" @punctuation.special
")" @punctuation.special) @embedded
(interpolationExpr
"\\#(" @punctuation.special
")" @punctuation.special) @embedded
(interpolationExpr
"\\##(" @punctuation.special
")" @punctuation.special) @embedded
(lineComment) @comment
(blockComment) @comment
(docComment) @comment
; Identifiers
(classProperty (identifier) @variable.other.member)
(objectProperty (identifier) @variable.other.member)
(parameterList (typedIdentifier (identifier) @variable.parameter))
(objectBodyParameters (typedIdentifier (identifier) @variable.parameter))
(identifier) @variable
; Method definitions
(classMethod (methodHeader (identifier)) @function.method)
(objectMethod (methodHeader (identifier)) @function.method)
; Method calls
(methodCallExpr
(identifier) @function.method)
; Types
(clazz (identifier) @type)
(typeAlias (identifier) @type)
((identifier) @type
(match? @type "^[A-Z]"))
(typeArgumentList
"<" @punctuation.bracket
">" @punctuation.bracket)

@ -0,0 +1,23 @@
; Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
;
; Licensed under the Apache License, Version 2.0 (the "License");
; you may not use this file except in compliance with the License.
; You may obtain a copy of the License at
;
; https://www.apache.org/licenses/LICENSE-2.0
;
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS,
; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
; See the License for the specific language governing permissions and
; limitations under the License.
; this definition is imprecise in that
; * any qualified or unqualified call to a method named "Regex" is considered a regex
; * string delimiters are considered part of the regex
[
(objectBody)
(classBody)
(ifExpr)
(mlStringLiteral) ; This isn't perfect; newlines are too indented but it's better than if omitted.
] @indent

@ -0,0 +1,30 @@
; Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
;
; Licensed under the Apache License, Version 2.0 (the "License");
; you may not use this file except in compliance with the License.
; You may obtain a copy of the License at
;
; https://www.apache.org/licenses/LICENSE-2.0
;
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS,
; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
; See the License for the specific language governing permissions and
; limitations under the License.
; this definition is imprecise in that
; * any qualified or unqualified call to a method named "Regex" is considered a regex
; * string delimiters are considered part of the regex
(
((methodCallExpr (identifier) @methodName (argumentList (slStringLiteral) @injection.content))
(#set! injection.language "regex"))
(eq? @methodName "Regex"))
((lineComment) @injection.content
(#set! injection.language "comment"))
((blockComment) @injection.content
(#set! injection.language "comment"))
((docComment) @injection.content
(#set! injection.language "markdown"))

@ -2,11 +2,29 @@
(#set! injection.language "comment")) (#set! injection.language "comment"))
((macro_invocation ((macro_invocation
macro: (identifier) @_html (#eq? @_html "html") macro:
[
(scoped_identifier
name: (_) @_macro_name)
(identifier) @_macro_name
]
(token_tree) @injection.content) (token_tree) @injection.content)
(#eq? @_macro_name "html")
(#set! injection.language "html") (#set! injection.language "html")
(#set! injection.include-children)) (#set! injection.include-children))
((macro_invocation
macro:
[
(scoped_identifier
name: (_) @_macro_name)
(identifier) @_macro_name
]
(token_tree) @injection.content)
(#eq? @_macro_name "slint")
(#set! injection.language "slint")
(#set! injection.include-children))
((macro_invocation ((macro_invocation
(token_tree) @injection.content) (token_tree) @injection.content)
(#set! injection.language "rust") (#set! injection.language "rust")

@ -53,20 +53,13 @@
(var_declaration (var_declaration
name: (identifier) @variable) name: (identifier) @variable)
; method definition ; function definitions/declarations
(class_definition (function_declaration
body: (template_body name: (identifier) @function.method)
(function_definition
name: (identifier) @function.method))) (function_definition
(object_definition name: (identifier) @function.method)
body: (template_body
(function_definition
name: (identifier) @function.method)))
(trait_definition
body: (template_body
(function_definition
name: (identifier) @function.method)))
; imports/exports ; imports/exports

@ -1,2 +1,16 @@
((comment) @injection.content ([(comment) (block_comment)] @injection.content
(#set! injection.language "comment")) (#set! injection.language "comment"))
; TODO for some reason multiline string (triple quotes) interpolation works only if it contains interpolated value
; Matches these SQL interpolators:
; - Doobie: 'sql', 'fr'
; - Quill: 'sql', 'infix'
; - Slick: 'sql', 'sqlu'
(interpolated_string_expression
interpolator:
((identifier) @interpolator
(#any-of? @interpolator "fr" "infix" "sql" "sqlu"))
(interpolated_string) @injection.content
(#set! injection.language "sql"))

@ -1,12 +1,15 @@
; Function queries ; Function queries
(function_definition (function_definition
body: (_) @function.inside) @function.around body: (_) @function.inside) @function.around ; Does not include end marker
; Does not match block lambdas or Scala 3 braceless lambdas
(lambda_expression (lambda_expression
(_) @function.inside) @function.around (_) @function.inside) @function.around
; Scala 3 braceless lambda
(colon_argument
(_) @function.inside) @function.around
; Class queries ; Class queries
@ -32,6 +35,9 @@
(parameters (parameters
((_) @parameter.inside . ","? @parameter.around) @parameter.around) ((_) @parameter.inside . ","? @parameter.around) @parameter.around)
(class_parameters
((_) @parameter.inside . ","? @parameter.around) @parameter.around)
(parameter_types (parameter_types
((_) @parameter.inside . ","? @parameter.around) @parameter.around) ((_) @parameter.inside . ","? @parameter.around) @parameter.around)

@ -1,122 +1,109 @@
(comment) @comment
; Different types:
(string_value) @string
(bool_value) @constant.builtin.boolean
; Constants
(escape_sequence) @constant.character.escape
(color_value) @constant
(identifier) @variable
[ [
(type_identifier) (children_identifier)
(units) (easing_kind_identifier)
]@type ] @constant.builtin
(array_literal [
(identifier) @type) (int_value)
(physical_length_value)
] @constant.numeric.integer
(function_identifier) @function
[ [
(image_macro) (float_value)
(children_macro) (percent_value)
(radial_grad_macro) (length_value)
(linear_grad_macro) (duration_value)
] @function.macro (angle_value)
(relative_font_size_value)
] @constant.numeric.float
(call_expression (purity) @keyword.storage.modifier
function: (identifier) @function)
(call_expression
function: (field_expression
field: (identifier) @function))
(vis) @keyword.control.import (function_visibility) @keyword.storage.modifier
(transition_statement state: (identifier) @variable.other.member) (property_visibility) @keyword.storage.modifier
(state_expression state: (identifier) @variable.other.member)
(struct_block_definition field: (identifier) @variable.other.member)
(assign_property (identifier) @attribute)
(comment) @comment (builtin_type_identifier) @type.builtin
(string_literal) @string (reference_identifier) @variable.builtin
(int_literal) @constant.numeric.integer
(float_literal) @constant.numeric.float
[ (type
"in" [
"in-out" (type_list)
"for" (user_type_identifier)
] @keyword.control.repeat (anon_struct_block)
]) @type
[ (user_type_identifier) @type
"import"
"export"
"from"
] @keyword.control.import
[ ; Functions and callbacks
"if" (argument) @variable.parameter
"else"
"when"
] @keyword.control.conditional
[ (function_call
"struct" name: (_) @function.call)
"property"
] @keyword.storage.type
[ ; definitions
"global" (callback
] @keyword.storage.modifier name: (_) @function)
(callback_alias
name: (_) @function)
[ (callback_event
"root" name: (simple_identifier) @function.call)
"parent"
"duration"
"easing"
] @variable.builtin
(enum_definition
name: (_) @type.enum)
[ (function_definition
"callback" name: (_) @function)
"animate"
"states"
"out"
"transitions"
"component"
"inherits"
] @keyword
[ (struct_definition
"black" name: (_) @type)
"transparent"
"blue" (typed_identifier
"ease" type: (_) @type)
"ease_in"
"ease-in" ; Operators
"ease_in_out" (binary_expression
"ease-in-out" op: (_) @operator)
"ease_out"
"ease-out" (unary_expression
"end" op: (_) @operator)
"green"
"red"
"start"
"yellow"
"white"
"gray"
] @constant.builtin
[ [
"true" (comparison_operator)
"false" (mult_prec_operator)
] @constant.builtin.boolean (add_prec_operator)
(unary_prec_operator)
(assignment_prec_operator)
] @operator
"@" @keyword [
":="
"=>"
"->"
"<=>"
] @operator
; ; Punctuation
[ [
","
"."
";" ";"
":" "."
","
] @punctuation.delimiter ] @punctuation.delimiter
; ; Brackets
[ [
"(" "("
")" ")"
@ -126,46 +113,136 @@
"}" "}"
] @punctuation.bracket ] @punctuation.bracket
(define_property ["<" ">"] @punctuation.bracket) (property
[
"<"
">"
] @punctuation.bracket)
[ ; Properties, constants and variables
"angle" (component
"bool" id: (simple_identifier) @constant)
"brush"
"color" (property
"duration" name: (simple_identifier) @variable)
"easing"
"float" (binding_alias
"image" name: (simple_identifier) @variable)
"int"
"length" (binding
"percent" name: (simple_identifier) @variable)
"physical-length"
"physical_length" (struct_block
"string" (simple_identifier) @variable.other.member)
] @type.builtin
(anon_struct_block
(simple_identifier) @variable.other.member)
(property_assignment
property: (simple_identifier) @variable)
(states_definition
name: (simple_identifier) @variable)
(callback
name: (simple_identifier) @variable)
(typed_identifier
name: (_) @variable)
(simple_indexed_identifier
(simple_identifier) @variable)
(expression
(simple_identifier) @variable)
; Attributes
[ [
":=" (linear_gradient_identifier)
"<=>" (radial_gradient_identifier)
"!" (radial_gradient_kind)
"-" ] @attribute
"+"
"*" (image_call
"/" "@image-url" @attribute)
"&&"
"||" (tr
">" "@tr" @attribute)
"<"
">=" ; Keywords
"<=" (animate_option_identifier) @keyword
"="
":" (export) @keyword.control.import
"+="
"-=" (if_statement
"*=" "if" @keyword.control.conditional)
"/="
"?" (if_expr
"=>" ] @operator [
"if"
(ternary_expression [":" "?"] @keyword.control.conditional) "else"
] @keyword.control.conditional)
(ternary_expression
[
"?"
":"
] @keyword.control.conditional)
(animate_statement
"animate" @keyword)
(callback
"callback" @keyword.function)
(component_definition
[
"component"
"inherits"
] @keyword.storage.type)
(enum_definition
"enum" @keyword.storage.type)
(for_loop
[
"for"
"in"
] @keyword.control.repeat)
(function_definition
"function" @keyword.function)
(global_definition
"global" @keyword.storage.type)
(imperative_block
"return" @keyword.control.return)
(import_statement
[
"import"
"from"
] @keyword.control.import)
(import_type
"as" @keyword.control.import)
(property
"property" @keyword.storage.type)
(states_definition
[
"states"
"when"
] @keyword)
(struct_definition
"struct" @keyword.storage.type)
(transitions_definition
[
"transitions"
"in"
"out"
] @keyword)

@ -1,12 +1,11 @@
[ [
(comp_body) (anon_struct_block)
(state_statement) (assignment_block)
(transition_statement) (block)
(handler_body) (enum_block)
(consequence_body) (global_block)
(global_single) (imperative_block)
(struct_block)
] @indent ] @indent
[ "}" @outdent
"}"
] @outdent

@ -1,3 +1,6 @@
; locals.scm [
(component)
(component_item) @local.scope (component_definition)
(function_definition)
(imperative_block)
] @local.scope

@ -0,0 +1,35 @@
(function_definition
(imperative_block) @funtion.inside) @function.around
(callback_event
(imperative_block) @function.inside) @function.around
(property
(imperative_block) @function.inside) @function.around
(struct_definition
(struct_block) @class.inside) @class.around
(enum_definition
(enum_block) @class.inside) @class.around
(global_definition
(global_block) @class.inside) @class.around
(component_definition
(block) @class.inside) @class.around
(component_definition
(block) @class.inside) @class.around
(comment) @comment.around
(typed_identifier
name: (_) @parameter.inside) @parameter.around
(callback
arguments: (_) @parameter.inside)
(string_value
"\"" . (_) @text.inside . "\"") @text.around

@ -0,0 +1,47 @@
; highlights.scm
[
"definition"
"caveat"
"permission"
"relation"
"nil"
] @keyword
[
","
":"
] @punctuation.delimiter
[
"("
")"
"{"
"}"
] @punctuation.bracket
[
"|"
"+"
"-"
"&"
"#"
"->"
"="
] @operator
("with") @keyword.operator
[
"nil"
"*"
] @constant.builtin
(comment) @comment
(type_identifier) @type
(cel_type_identifier) @type
(cel_variable_identifier) @variable.parameter
(field_identifier) @variable.other.member
[
(func_identifier)
(method_identifier)
] @function.method

@ -0,0 +1,5 @@
((comment) @injection.content
(#set! injection.language "comment"))
((caveat_expr) @injection.content
(#set! injection.language "cel"))

@ -0,0 +1,4 @@
(object_definition
name: (type_identifier) @name) @definition.type
(type_identifier) @name @reference.type

@ -1,10 +1,10 @@
; Upstream: https://github.com/alex-pinkus/tree-sitter-swift/blob/8d2fd80e3322df51e3f70952e60d57f5d4077eb8/queries/highlights.scm ; Upstream: https://github.com/alex-pinkus/tree-sitter-swift/blob/1c586339fb00014b23d6933f2cc32b588a226f3b/queries/highlights.scm
(line_string_literal (line_string_literal
["\\(" ")"] @punctuation.special) ["\\(" ")"] @punctuation.special)
["." ";" ":" "," ] @punctuation.delimiter ["." ";" ":" "," ] @punctuation.delimiter
["(" ")" "[" "]" "{" "}"] @punctuation.bracket ; TODO: "\\(" ")" in interpolations should be @punctuation.special ["(" ")" "[" "]" "{" "}"] @punctuation.bracket
; Identifiers ; Identifiers
(attribute) @variable (attribute) @variable
@ -26,6 +26,7 @@
(function_declaration "init" @constructor) (function_declaration "init" @constructor)
(throws) @keyword (throws) @keyword
"async" @keyword "async" @keyword
"await" @keyword
(where_keyword) @keyword (where_keyword) @keyword
(parameter external_name: (simple_identifier) @variable.parameter) (parameter external_name: (simple_identifier) @variable.parameter)
(parameter name: (simple_identifier) @variable.parameter) (parameter name: (simple_identifier) @variable.parameter)
@ -48,6 +49,7 @@
"convenience" "convenience"
"required" "required"
"some" "some"
"any"
] @keyword ] @keyword
[ [

@ -0,0 +1,298 @@
; See: https://docs.helix-editor.com/master/themes.html#syntax-highlighting
; -------------------------------------------------------------------------
; attribute
; ---------
[
"@name"
"@interface"
] @attribute
; comment.line
; ------------
((comment) @comment.line
(#match? @comment.line "^//"))
; comment.block
; -------------
(comment) @comment.block
; function.builtin
; ----------------
((identifier) @function.builtin
(#any-of? @function.builtin
"send" "sender" "require" "now"
"myBalance" "myAddress" "newAddress"
"contractAddress" "contractAddressExt"
"emit" "cell" "ton"
"beginString" "beginComment" "beginTailString" "beginStringFromBuilder" "beginCell" "emptyCell"
"randomInt" "random"
"checkSignature" "checkDataSignature" "sha256"
"min" "max" "abs" "pow"
"throw" "dump" "getConfigParam"
"nativeThrowWhen" "nativeThrowUnless" "nativeReserve"
"nativeRandomize" "nativeRandomizeLt" "nativePrepareRandom" "nativeRandom" "nativeRandomInterval")
(#is-not? local))
; function.method
; ---------------
(method_call_expression
name: (identifier) @function.method)
; function
; --------
(func_identifier) @function
(native_function
name: (identifier) @function)
(static_function
name: (identifier) @function)
(static_call_expression
name: (identifier) @function)
(init_function
"init" @function.method)
(receive_function
"receive" @function.method)
(bounced_function
"bounced" @function.method)
(external_function
"external" @function.method)
(function
name: (identifier) @function.method)
; keyword.control.conditional
; ---------------------------
[
"if" "else"
] @keyword.control.conditional
; keyword.control.repeat
; ----------------------
[
"while" "repeat" "do" "until"
] @keyword.control.repeat
; keyword.control.import
; ----------------------
"import" @keyword.control.import
; keyword.control.return
; ----------------------
"return" @keyword.control.return
; keyword.operator
; ----------------
"initOf" @keyword.operator
; keyword.directive
; -----------------
"primitive" @keyword.directive
; keyword.function
; ----------------
[
"fun"
"native"
] @keyword.function
; keyword.storage.type
; --------------------
[
"contract" "trait" "struct" "message" "with"
"const" "let"
] @keyword.storage.type
; keyword.storage.modifier
; ------------------------
[
"get" "mutates" "extends" "virtual" "override" "inline" "abstract"
] @keyword.storage.modifier
; keyword
; -------
[
"with"
; "public" ; -- not used, but declared in grammar.ohm
; "extend" ; -- not used, but declared in grammar.ohm
] @keyword
; constant.builtin.boolean
; ------------------------
(boolean) @constant.builtin.boolean
; constant.builtin
; ----------------
((identifier) @constant.builtin
(#any-of? @constant.builtin
"SendPayGasSeparately"
"SendIgnoreErrors"
"SendDestroyIfZero"
"SendRemainingValue"
"SendRemainingBalance")
(#is-not? local))
(null) @constant.builtin
; constant.numeric.integer
; ------------------------
(integer) @constant.numeric.integer
; constant
; --------
(constant
name: (identifier) @constant)
; string.special.path
; -------------------
(import_statement
library: (string) @string.special.path)
; string
; ------
(string) @string
; type.builtin
; ------------
(tlb_serialization
"as" @keyword
type: (identifier) @type.builtin
(#any-of? @type.builtin
"int8" "int16" "int32" "int64" "int128" "int256" "int257"
"uint8" "uint16" "uint32" "uint64" "uint128" "uint256"
"coins" "remaining" "bytes32" "bytes64"))
((type_identifier) @type.builtin
(#any-of? @type.builtin
"Address" "Bool" "Builder" "Cell" "Int" "Slice" "String" "StringBuilder"))
(map_type
"map" @type.builtin
"<" @punctuation.bracket
">" @punctuation.bracket)
(bounced_type
"bounced" @type.builtin
"<" @punctuation.bracket
">" @punctuation.bracket)
((identifier) @type.builtin
(#eq? @type.builtin "SendParameters")
(#is-not? local))
; type
; ----
(type_identifier) @type
; constructor
; -----------
(instance_expression
name: (identifier) @constructor)
(initOf
name: (identifier) @constructor)
; operator
; --------
[
"-" "-="
"+" "+="
"*" "*="
"/" "/="
"%" "%="
"=" "=="
"!" "!=" "!!"
"<" "<=" "<<"
">" ">=" ">>"
"&" "|"
"&&" "||"
] @operator
; punctuation.bracket
; -------------------
[
"(" ")"
"{" "}"
] @punctuation.bracket
; punctuation.delimiter
; ---------------------
[
";"
","
"."
":"
"?"
] @punctuation.delimiter
; variable.other.member
; ---------------------
(field
name: (identifier) @variable.other.member)
(contract_body
(constant
name: (identifier) @variable.other.member))
(trait_body
(constant
name: (identifier) @variable.other.member))
(field_access_expression
name: (identifier) @variable.other.member)
(lvalue (_) (_) @variable.other.member)
(instance_argument
name: (identifier) @variable.other.member)
; variable.parameter
; ------------------
(parameter
name: (identifier) @variable.parameter)
; variable.builtin
; ----------------
(self) @variable.builtin
; variable
; --------
(identifier) @variable

@ -0,0 +1,38 @@
; indent
; ------
[
; (..., ...)
(parameter_list)
(argument_list)
; {..., ...}
(instance_argument_list)
; {...; ...}
(message_body)
(struct_body)
(contract_body)
(trait_body)
(function_body)
(block_statement)
; misc.
(binary_expression)
(return_statement)
] @indent
; outdent
; -------
[
"}"
")"
">"
] @outdent
; indent.always
; outdent.always
; align
; extend
; extend.prevent-once

@ -0,0 +1,5 @@
; See: https://docs.helix-editor.com/guides/injection.html
((comment) @injection.content
(#set! injection.language "comment")
(#match? @injection.content "^//"))

@ -0,0 +1,35 @@
; See: https://tree-sitter.github.io/tree-sitter/syntax-highlighting#local-variables
; Scopes @local.scope
; -------------------------
[
(static_function)
(init_function)
(bounced_function)
(receive_function)
(external_function)
(function)
(block_statement)
] @local.scope
; Definitions @local.definition
; ------------------------------
(let_statement
name: (identifier) @local.definition)
(parameter
name: (identifier) @local.definition)
(constant
name: (identifier) @local.definition)
; References @local.reference
; -----------------------------
(self) @local.reference
(value_expression (identifier) @local.reference)
(lvalue (identifier) @local.reference)

@ -0,0 +1,58 @@
; function.inside & around
; ------------------------
(static_function
body: (_) @function.inside) @function.around
(init_function
body: (_) @function.inside) @function.around
(bounced_function
body: (_) @function.inside) @function.around
(receive_function
body: (_) @function.inside) @function.around
(external_function
body: (_) @function.inside) @function.around
(function
body: (_) @function.inside) @function.around
; class.inside & around
; ---------------------
(struct
body: (_) @class.inside) @class.around
(message
body: (_) @class.inside) @class.around
(contract
body: (_) @class.inside) @class.around
; NOTE: Marked as @definition.interface in tags, as it's semantically correct
(trait
body: (_) @class.inside) @class.around
; parameter.inside & around
; -------------------------
(parameter_list
((_) @parameter.inside . ","? @parameter.around) @parameter.around)
(argument_list
((_) @parameter.inside . ","? @parameter.around) @parameter.around)
(instance_argument_list
((_) @parameter.inside . ","? @parameter.around) @parameter.around)
; comment.inside
; --------------
(comment) @comment.inside
; comment.around
; --------------
(comment)+ @comment.around

@ -9,8 +9,6 @@
;; Keywords ;; Keywords
[ [
(kw_forall) (kw_forall)
(unique_kw)
(structural_kw)
(type_kw) (type_kw)
(kw_equals) (kw_equals)
(do) (do)
@ -51,7 +49,7 @@
(blank_pattern) @variable.builtin (blank_pattern) @variable.builtin
;; Types ;; Types
(record_field name: (wordy_id) @variable.other.member type: (wordy_id) @type) (record_field name: (wordy_id) @variable.other.member type: (_) @type)
(type_constructor (type_name (wordy_id) @constructor)) (type_constructor (type_name (wordy_id) @constructor))
(ability_declaration type_name: (wordy_id) @type type_arg: (wordy_id) @variable.parameter) (ability_declaration type_name: (wordy_id) @type type_arg: (wordy_id) @variable.parameter)
(effect (wordy_id) @special) ;; NOTE: an effect is just like a type, but in signature we special case it (effect (wordy_id) @special) ;; NOTE: an effect is just like a type, but in signature we special case it

@ -0,0 +1,15 @@
[
(term_definition)
(type_declaration)
(pattern)
(tuple_or_parenthesized)
(literal_list)
(tuple_pattern)
(function_application)
(exp_if)
(constructor)
(delay_block)
(type_signature)
] @indent
[(kw_then) (kw_else) (cases)] @indent.always @extend

@ -0,0 +1,118 @@
attribute_color = "attribute_color"
keyword = "keyword_foreground_color"
"keyword.directive" = "light_blue"
namespace = "light_blue"
punctuation = "punctuation_color"
"punctuation.delimiter" = "punctuation_color"
operator = "operator_color"
special = "label"
"variable.other.member" = "white"
variable = "variable"
"variable.parameter" = { fg = "variable" }
"variable.builtin" = {fg = "built_in", modifiers=["bold","italic"]}
type = "white"
"type.builtin" = "white"
constructor = "light_blue"
function = "white"
"function.macro" = {fg ="light_blue" }
"function.builtin" = "white"
tag = "tag"
comment = { fg = "comment_color", modifiers = ["italic"] }
constant = {fg ="white"}
"constant.builtin" = "white"
string = {fg="string", modifiers=["italic"]}
"constant.numeric" = "constant_numeric_foreground_color"
"constant.character.escape" = "label"
# used for lifetimes
label = "label"
"markup.heading" = "light_blue"
"markup.bold" = { modifiers = ["bold"] }
"markup.italic" = { modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "link_url_foreground_color", modifiers = ["underlined"] }
"markup.link.text" = "markup_link_foreground_color"
"markup.raw" = "markup_raw_foreground_color"
"diff.plus" = "#35bf86"
"diff.minus" = "#f22c86"
"diff.delta" = "info"
"ui.background" = { bg = "black" }
"ui.background.separator" = { fg = "window_color" }
"ui.linenr" = { fg = "window_color" }
"ui.linenr.selected" = { fg = "light_blue" }
"ui.statusline" = { fg = "statusline_foreground_color", bg = "black" }
"ui.statusline.inactive" = { fg = "statusline_inactive_foreground_color", bg = "black" }
"ui.virtual.ruler" = { bg = "dark"}
"ui.popup" = { fg = "menu_normal_text_color", bg = "menu_background_color" }
"ui.window" = { fg = "dark"}
"ui.help" = { fg = "menu_normal_text_color", bg = "menu_background_color" }
"ui.text" = { fg = "text" }
"ui.text.focus" = { fg = "white" }
"ui.text.inactive" = "comment_color"
"ui.virtual" = { fg = "#008DFF" }
"ui.virtual.indent-guide" = { fg = "window_color" }
"ui.selection" = { bg = "#4f46e5" }
"ui.selection.primary" = { bg = "#4f46e5" }
"ui.cursor.select" = { bg = "cursor_normal_bg_color" }
"ui.cursor.primary.insert" = { bg = "#f43f5e", fg = "white" }
"ui.cursor.match" = { fg = "#212121", bg = "#6C6999" }
"ui.cursorline.primary" = { bg = "dark"}
"ui.highlight" = { bg = "dark" }
"ui.highlight.frameline" = { bg = "#634450" }
"ui.debug" = { fg = "#634450" }
"ui.debug.breakpoint" = { fg = "debug_breakpoint" }
"ui.menu" = { fg = "menu_normal_text_color", bg = "menu_background_color" }
"ui.menu.selected" = { fg = "menu_background_color", bg = "white" }
"ui.menu.scroll" = { fg = "menu_scroll", bg = "window_color" }
"diagnostic.hint" = { underline = { color = "hint", style = "curl" } }
"diagnostic.info" = { underline = { color = "info", style = "curl" } }
"diagnostic.warning" = { underline = { color = "warning", style = "curl" } }
"diagnostic.error" = { underline = { color = "error", style = "curl" } }
warning = "warning"
error = "#f43f5e"
info = "info"
hint = "#38bdf8"
[palette]
label = "#efba5d"
constant_numeric_foreground_color = "#E8DCA0"
tag = "#eccdba"
markup_link_foreground_color = "#eccdba"
markup_raw_foreground_color = "#eccdba"
keyword_foreground_color="#eccdba" # alternative color "#ecc1ba"
comment_color = "#697C81"
link_url_foreground_color="#b8b8b8"
debug_breakpoint = "#f47868"
window_color = "#484a4d"
light_blue = "#bee0ec" #change name
text="#bfdbfe"
black = "#000000"
white = "#ffffff"
dark= "#111111"
punctuation_color = "#a4a0e8"
string="#6ee7b7"
attribute_color="#dbbfef"
operator_color="#bee0ec"
menu_background_color="#1e3a8a"
menu_normal_text_color="#93c5fd"
statusline_active_background_color="#111111"
statusline_inactive_background_color="#0e0e0e"
statusline_inactive_foreground_color="#b8b8b8"
popup_background_color="#1e3a8a"
cursor_normal_bg_color="#6366f1"
warning="#ffcd1c"
error = "#f43f5e"
hint = "#38bdf8"
info = "#6366f1"
variable="#c7d2fe"
menu_scroll="#93c5fd"
built_in="#10b981"
statusline_foreground_color="#6366f1"

@ -95,7 +95,8 @@
"ui.text" = "shade05" "ui.text" = "shade05"
"ui.text.focus" = { fg = "shade07", bg = "light_blue" } "ui.text.focus" = { fg = "shade07", bg = "light_blue" }
"ui.virtual" = "shade03" "ui.virtual" = "shade03"
"ui.virtual.ruler" = { bg = "shade04" } "ui.virtual.ruler" = { bg = "shade01" }
"ui.virtual.inlay-hint" = { fg = "shade03_darker" }
"ui.menu" = { fg = "shade05", bg = "shade01" } "ui.menu" = { fg = "shade05", bg = "shade01" }
"ui.menu.selected" = { fg = "shade07", bg = "light_blue" } "ui.menu.selected" = { fg = "shade07", bg = "light_blue" }
@ -119,6 +120,9 @@ shade05 = "#434b6c"
shade06 = "#343a54" shade06 = "#343a54"
shade07 = "#25293c" shade07 = "#25293c"
shade03_darker = "#9199bb"
shade04_lighter = "#616d9d"
background = "#f2f3f7" background = "#f2f3f7"
foreground = "#25293c" foreground = "#25293c"
@ -133,7 +137,6 @@ blue = "#0073E6"
dark_blue = "#185b93" dark_blue = "#185b93"
darker_blue = "#000080" darker_blue = "#000080"
purple = "#660E7A" purple = "#660E7A"
light_purple = "#ED9CFF" light_purple = "#ED9CFF"
@ -142,7 +145,6 @@ green = "#00733B"
light_green = "#5DCE87" light_green = "#5DCE87"
green_blue = "#458383" green_blue = "#458383"
yellow = "#808000" yellow = "#808000"
dark_yellow = "#7A7A43" dark_yellow = "#7A7A43"

@ -2,134 +2,153 @@
# Author : Chirikumbrah # Author : Chirikumbrah
"annotation" = { fg = "foreground" } "annotation" = { fg = "foreground" }
"attribute" = { fg = "green", modifiers = ["italic"] } "attribute" = { fg = "green", modifiers = ["italic"] }
"comment" = { fg = "comment" } "comment" = { fg = "comment" }
"comment.block.documentation" = { fg = "comment" }
"comment.block" = { fg = "comment" } "comment.block" = { fg = "comment" }
"comment.block.documentation" = { fg = "comment" }
"comment.line" = { fg = "comment" } "comment.line" = { fg = "comment" }
"constant" = { fg = "purple" } "constant" = { fg = "purple" }
"constant.numeric" = { fg = "purple" }
"constant.builtin" = { fg = "purple" } "constant.builtin" = { fg = "purple" }
"constant.builtin.boolean" = { fg = "purple" } "constant.builtin.boolean" = { fg = "purple" }
"constant.character" = { fg = "cyan" } "constant.character" = { fg = "cyan" }
"constant.character.escape" = { fg = "pink" } "constant.character.escape" = { fg = "pink" }
"constant.macro" = { fg = "purple" } "constant.macro" = { fg = "purple" }
"constant.numeric" = { fg = "purple" }
"constructor" = { fg = "purple" } "constructor" = { fg = "purple" }
"definition" = { underline = { color = "cyan" } }
"diagnostic" = { underline = { color = "orange", style = "curl" } }
"diagnostic.hint" = { underline = { color = "purple", style = "curl" } }
"diagnostic.warning" = { underline = { color = "yellow", style = "curl" } }
"diagnostic.error" = { underline = { color = "red", style = "curl" } }
"diagnostic.info" = { underline = { color = "cyan", style = "curl" } }
"error" = { fg = "red" }
"hint" = { fg = "purple" }
"info" = { fg = "cyan" }
"warning" = { fg = "yellow" }
"diff.delta" = { fg = "orange" }
"diff.minus" = { fg = "red" }
"diff.plus" = { fg = "green" }
# d
"function" = { fg = "green" } "function" = { fg = "green" }
"function.builtin" = { fg = "green" } "function.builtin" = { fg = "green" }
"function.method" = { fg = "green" }
"function.macro" = { fg = "purple" }
"function.call" = { fg = "green" } "function.call" = { fg = "green" }
"function.macro" = { fg = "purple" }
"function.method" = { fg = "green" }
"keyword" = { fg = "pink" } "keyword" = { fg = "pink" }
"keyword.operator" = { fg = "pink" }
"keyword.function" = { fg = "pink" }
"keyword.return" = { fg = "pink" }
"keyword.control.import" = { fg = "pink" }
"keyword.directive" = { fg = "green" }
"keyword.control.repeat" = { fg = "pink" }
"keyword.control.conditional" = { fg = "pink" } "keyword.control.conditional" = { fg = "pink" }
"keyword.control.exception" = { fg = "purple" } "keyword.control.exception" = { fg = "purple" }
"keyword.control.import" = { fg = "pink" }
"keyword.control.repeat" = { fg = "pink" }
"keyword.directive" = { fg = "green" }
"keyword.function" = { fg = "pink" }
"keyword.operator" = { fg = "pink" }
"keyword.return" = { fg = "pink" }
"keyword.storage" = { fg = "pink" } "keyword.storage" = { fg = "pink" }
"keyword.storage.type" = { fg = "cyan", modifiers = ["italic"] }
"keyword.storage.modifier" = { fg = "pink" } "keyword.storage.modifier" = { fg = "pink" }
"tag" = { fg = "pink" } "keyword.storage.type" = { fg = "cyan", modifiers = ["italic"] }
"tag.attribute" = { fg = "purple" }
"tag.delimiter" = { fg = "foreground" }
"label" = { fg = "cyan" } "label" = { fg = "cyan" }
"markup.bold" = { fg = "orange", modifiers = ["bold"] }
"markup.heading" = { fg = "purple", modifiers = ["bold"] }
"markup.italic" = { fg = "yellow", modifiers = ["italic"] }
"markup.link.text" = { fg = "pink" }
"markup.link.url" = { fg = "cyan" }
"markup.list" = { fg = "cyan" }
"markup.quote" = { fg = "yellow", modifiers = ["italic"] }
"markup.raw" = { fg = "foreground" }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"punctuation" = { fg = "foreground" } "punctuation" = { fg = "foreground" }
"punctuation.bracket" = { fg = "foreground" } "punctuation.bracket" = { fg = "foreground" }
"punctuation.delimiter" = { fg = "foreground" } "punctuation.delimiter" = { fg = "foreground" }
"punctuation.special" = { fg = "pink" } "punctuation.special" = { fg = "pink" }
"special" = { fg = "pink" } "special" = { fg = "pink" }
"string" = { fg = "yellow" } "string" = { fg = "yellow" }
"string.regexp" = { fg = "red" }
"string.special" = { fg = "orange" } "string.special" = { fg = "orange" }
"string.symbol" = { fg = "yellow" } "string.symbol" = { fg = "yellow" }
"string.regexp" = { fg = "red" }
"type.builtin" = { fg = "cyan" } "tag" = { fg = "pink" }
"tag.attribute" = { fg = "purple" }
"tag.delimiter" = { fg = "foreground" }
"type" = { fg = "cyan", modifiers = ["italic"] } "type" = { fg = "cyan", modifiers = ["italic"] }
"type.builtin" = { fg = "cyan" }
"type.enum.variant" = { fg = "foreground", modifiers = ["italic"] } "type.enum.variant" = { fg = "foreground", modifiers = ["italic"] }
"variable" = { fg = "foreground" }
"variable.builtin" = { fg = "purple", modifiers = ["italic"] }
"variable.parameter" = { fg = "orange", modifiers = ["italic"] }
"variable.other" = { fg = "foreground" }
"variable.other.member" = { fg = "foreground" }
"diff.plus" = { fg = "green" }
"diff.delta" = { fg = "orange" }
"diff.minus" = { fg = "red" }
"ui.background" = { fg = "foreground", bg = "background" } "ui.background" = { fg = "foreground", bg = "background" }
"ui.cursor" = { fg = "background", bg = "purple", modifiers = ["dim"] }
"ui.cursor.insert" = { fg = "background", bg = "green", modifiers = ["dim"] }
"ui.cursor.match" = { fg = "foreground", bg = "grey" } "ui.cursor.match" = { fg = "foreground", bg = "grey" }
"ui.cursor" = { fg = "background", bg = "purple", modifiers = ["dim"] } "ui.cursor.normal" = { fg = "background", bg = "purple", modifiers = ["dim"] }
"ui.cursor.normal" = { fg = "background", bg = "purple", modifiers = ["dim"] }
"ui.cursor.insert" = { fg = "background", bg = "green", modifiers = ["dim"] }
"ui.cursor.select" = { fg = "background", bg = "cyan", modifiers = ["dim"] }
"ui.cursor.primary.normal" = { fg = "background", bg = "purple" }
"ui.cursor.primary.insert" = { fg = "background", bg = "green" } "ui.cursor.primary.insert" = { fg = "background", bg = "green" }
"ui.cursor.primary.normal" = { fg = "background", bg = "purple" }
"ui.cursor.primary.select" = { fg = "background", bg = "cyan" } "ui.cursor.primary.select" = { fg = "background", bg = "cyan" }
"ui.cursor.select" = { fg = "background", bg = "cyan", modifiers = ["dim"] }
"ui.cursorline.primary" = { bg = "cursorline" } "ui.cursorline.primary" = { bg = "cursorline" }
"ui.help" = { fg = "foreground", bg = "black" }
"ui.debug" = { fg = "red" } "ui.debug" = { fg = "red" }
"ui.help" = { fg = "foreground", bg = "black" }
"ui.highlight.frameline" = { fg = "background", bg = "red" } "ui.highlight.frameline" = { fg = "background", bg = "red" }
"ui.linenr" = { fg = "comment" } "ui.linenr" = { fg = "comment" }
"ui.linenr.selected" = { fg = "foreground" } "ui.linenr.selected" = { fg = "foreground" }
"ui.menu" = { fg = "foreground", bg = "current_line" } "ui.menu" = { fg = "foreground", bg = "current_line" }
"ui.menu.selected" = { fg = "current_line", bg = "purple", modifiers = ["dim"] }
"ui.menu.scroll" = { fg = "foreground", bg = "current_line" } "ui.menu.scroll" = { fg = "foreground", bg = "current_line" }
"ui.menu.selected" = { fg = "current_line", bg = "purple", modifiers = ["dim"] }
"ui.popup" = { fg = "foreground", bg = "black" } "ui.popup" = { fg = "foreground", bg = "black" }
"ui.selection.primary" = { bg = "current_line" }
"ui.selection" = { bg = "selection" } "ui.selection" = { bg = "selection" }
"ui.selection.primary" = { bg = "current_line" }
"ui.statusline" = { fg = "foreground", bg = "darker" } "ui.statusline" = { fg = "foreground", bg = "darker" }
"ui.statusline.inactive" = { fg = "comment", bg = "darker" } "ui.statusline.inactive" = { fg = "comment", bg = "darker" }
"ui.statusline.normal" = { fg = "black", bg = "purple" } "ui.statusline.insert" = { fg = "black", bg = "green", modifiers = ["bold"] }
"ui.statusline.insert" = { fg = "black", bg = "green" } "ui.statusline.normal" = { fg = "black", bg = "purple", modifiers = ["bold"] }
"ui.statusline.select" = { fg = "black", bg = "cyan" } "ui.statusline.select" = { fg = "black", bg = "cyan", modifiers = ["bold"] }
"ui.text" = { fg = "foreground" } "ui.text" = { fg = "foreground" }
"ui.text.focus" = { fg = "cyan" } "ui.text.focus" = { fg = "cyan" }
"ui.window" = { fg = "foreground" }
"ui.virtual.whitespace" = { fg = "current_line" }
"ui.virtual.wrap" = { fg = "current_line" }
"ui.virtual.ruler" = { bg = "black" }
"ui.virtual.indent-guide" = { fg = "indent" } "ui.virtual.indent-guide" = { fg = "indent" }
"ui.virtual.inlay-hint" = { fg = "cyan" } "ui.virtual.inlay-hint" = { fg = "cyan" }
"ui.virtual.inlay-hint.parameter" = { fg = "cyan", modifiers = ["italic", "dim"] } "ui.virtual.inlay-hint.parameter" = { fg = "cyan", modifiers = ["italic", "dim"] }
"ui.virtual.inlay-hint.type" = { fg = "cyan", modifiers = ["italic", "dim"] } "ui.virtual.inlay-hint.type" = { fg = "cyan", modifiers = ["italic", "dim"] }
"hint" = { fg = "purple" } "ui.virtual.ruler" = { bg = "black" }
"error" = { fg = "red" } "ui.virtual.whitespace" = { fg = "whitespace" }
"warning" = { fg = "yellow" } "ui.virtual.wrap" = { fg = "current_line" }
"info" = { fg = "cyan" } "ui.window" = { fg = "foreground" }
"markup.heading" = { fg = "purple", modifiers = ["bold"] }
"markup.list" = { fg = "cyan" } "variable" = { fg = "foreground" }
"markup.bold" = { fg = "orange", modifiers = ["bold"] } "variable.builtin" = { fg = "purple", modifiers = ["italic"] }
"markup.italic" = { fg = "yellow", modifiers = ["italic"] } "variable.other" = { fg = "foreground" }
"markup.strikethrough" = { modifiers = ["crossed_out"] } "variable.other.member" = { fg = "foreground" }
"markup.link.url" = { fg = "cyan" } "variable.parameter" = { fg = "orange", modifiers = ["italic"] }
"markup.link.text" = { fg = "pink" }
"markup.quote" = { fg = "yellow", modifiers = ["italic"] }
"markup.raw" = { fg = "foreground" }
"diagnostic" = { underline = { color = "orange", style = "curl" } }
"diagnostic.hint" = { underline = { color = "purple", style = "curl" } }
"diagnostic.warning" = { underline = { color = "yellow", style = "curl" } }
"diagnostic.error" = { underline = { color = "red", style = "curl" } }
"diagnostic.info" = { underline = { color = "cyan", style = "curl" } }
"definition" = { underline = { color = "cyan" } }
[palette] [palette]
foreground = "#f8f8f2"
background = "#282A36" background = "#282A36"
cursorline = "#2d303e"
darker = "#222430"
black = "#191A21" black = "#191A21"
grey = "#666771"
comment = "#6272A4" comment = "#6272A4"
current_line = "#44475a" current_line = "#44475a"
cursorline = "#2d303e"
cyan = "#8be9fd"
darker = "#222430"
foreground = "#f8f8f2"
green = "#50fa7b"
grey = "#666771"
indent = "#56596a" indent = "#56596a"
selection = "#363848"
red = "#ff5555"
orange = "#ffb86c" orange = "#ffb86c"
yellow = "#f1fa8c"
green = "#50fa7b"
purple = "#BD93F9"
cyan = "#8be9fd"
pink = "#ff79c6" pink = "#ff79c6"
purple = "#BD93F9"
red = "#ff5555"
selection = "#363848"
whitespace = "#586693"
yellow = "#f1fa8c"

@ -60,6 +60,7 @@ label = "scale.red.3"
"ui.text.focus" = { fg = "fg.default" } "ui.text.focus" = { fg = "fg.default" }
"ui.text.inactive" = "fg.subtle" "ui.text.inactive" = "fg.subtle"
"ui.virtual" = { fg = "scale.gray.6" } "ui.virtual" = { fg = "scale.gray.6" }
"ui.virtual.ruler" = { bg = "canvas.subtle" }
"ui.selection" = { bg = "scale.blue.8" } "ui.selection" = { bg = "scale.blue.8" }
"ui.selection.primary" = { bg = "scale.blue.7" } "ui.selection.primary" = { bg = "scale.blue.7" }

@ -60,6 +60,7 @@ label = "scale.red.5"
"ui.text.focus" = { fg = "fg.default" } "ui.text.focus" = { fg = "fg.default" }
"ui.text.inactive" = "fg.subtle" "ui.text.inactive" = "fg.subtle"
"ui.virtual" = { fg = "scale.gray.2" } "ui.virtual" = { fg = "scale.gray.2" }
"ui.virtual.ruler" = { bg = "canvas.subtle" }
"ui.selection" = { bg = "scale.blue.0" } "ui.selection" = { bg = "scale.blue.0" }
"ui.selection.primary" = { bg = "scale.blue.1" } "ui.selection.primary" = { bg = "scale.blue.1" }

@ -0,0 +1,7 @@
# Author : Twinkle <saintwinkle@gmail.com>
# The theme uses the gruvbox light palette with hard contrast: github.com/morhetz/gruvbox
inherits = "gruvbox_light"
[palette]
bg0 = "#f9f5d7" # main background

@ -0,0 +1,7 @@
# Author : Twinkle <saintwinkle@gmail.com>
# The theme uses the gruvbox light palette with soft contrast: github.com/morhetz/gruvbox
inherits = "gruvbox_light"
[palette]
bg0 = "#f2e5bc" # main background

@ -11,6 +11,7 @@ keyword = "purple"
function = "blue" function = "blue"
label = "orange" label = "orange"
type = "orange" type = "orange"
constructor = "orange"
namespace = "orange" namespace = "orange"
# User Interface # User Interface

@ -73,9 +73,9 @@
"ui.statusline" = { fg = "white", bg = "light-black" } "ui.statusline" = { fg = "white", bg = "light-black" }
"ui.statusline.inactive" = { fg = "light-gray", bg = "light-black" } "ui.statusline.inactive" = { fg = "light-gray", bg = "light-black" }
"ui.statusline.normal" = { fg = "light-black", bg = "blue" } "ui.statusline.normal" = { fg = "light-black", bg = "blue", modifiers = ["bold"] }
"ui.statusline.insert" = { fg = "light-black", bg = "green" } "ui.statusline.insert" = { fg = "light-black", bg = "green", modifiers = ["bold"] }
"ui.statusline.select" = { fg = "light-black", bg = "purple" } "ui.statusline.select" = { fg = "light-black", bg = "purple", modifiers = ["bold"] }
"ui.bufferline" = { fg = "light-gray", bg = "light-black" } "ui.bufferline" = { fg = "light-gray", bg = "light-black" }
"ui.bufferline.active" = { fg = "light-black", bg = "blue", underline = { color = "light-black", style = "line" } } "ui.bufferline.active" = { fg = "light-black", bg = "blue", underline = { color = "light-black", style = "line" } }

@ -63,6 +63,9 @@
"ui.cursorline.primary" = { bg = "bg1" } "ui.cursorline.primary" = { bg = "bg1" }
"ui.statusline" = { fg = "fg", bg = "bg3" } "ui.statusline" = { fg = "fg", bg = "bg3" }
"ui.statusline.inactive" = { fg = "grey", bg = "bg1" } "ui.statusline.inactive" = { fg = "grey", bg = "bg1" }
"ui.statusline.normal" = { fg = "bg0", bg = "blue", modifiers = ["bold"] }
"ui.statusline.insert" = { fg = "bg0", bg = "green", modifiers = ["bold"] }
"ui.statusline.select" = { fg = "bg0", bg = "purple", modifiers = ["bold"] }
"ui.popup" = { fg = "grey", bg = "bg2" } "ui.popup" = { fg = "grey", bg = "bg2" }
"ui.window" = { fg = "grey", bg = "bg0" } "ui.window" = { fg = "grey", bg = "bg0" }
"ui.help" = { fg = "fg", bg = "bg1" } "ui.help" = { fg = "fg", bg = "bg1" }
@ -71,7 +74,7 @@
"ui.menu" = { fg = "fg", bg = "bg2" } "ui.menu" = { fg = "fg", bg = "bg2" }
"ui.menu.selected" = { fg = "bg0", bg = "green" } "ui.menu.selected" = { fg = "bg0", bg = "green" }
"ui.virtual.whitespace" = { fg = "grey_dim" } "ui.virtual.whitespace" = { fg = "grey_dim" }
"ui.virtual.ruler" = { bg = "grey_dim" } "ui.virtual.ruler" = { bg = "bg3" }
"ui.virtual.inlay-hint" = { fg = "grey_dim" } "ui.virtual.inlay-hint" = { fg = "grey_dim" }
info = { fg = 'green', bg = 'bg2' } info = { fg = 'green', bg = 'bg2' }

@ -0,0 +1,80 @@
# Author: dgkf
"ui.background" = { }
"ui.background.separator" = { fg = "red" }
"ui.cursor" = { fg = "light-gray", modifiers = ["reversed"] }
"ui.cursor.match" = { fg = "light-yellow", modifiers = ["reversed"] }
"ui.cursor.primary" = { fg = "light-gray", modifiers = ["reversed"] }
"ui.cursor.secondary" = { fg = "gray", modifiers = ["reversed"] }
"ui.cursorline.primary" = { bg = "black" }
"ui.gutter" = { }
"ui.gutter.selected" = { bg = "black" }
"ui.help" = { fg = "white", bg = "black" }
"ui.linenr" = { fg = "gray", modifiers = ["bold"] }
"ui.linenr.selected" = { fg = "white", modifiers = ["bold"] }
"ui.menu" = { fg = "light-gray", bg = "gray" }
"ui.menu.selected" = { modifiers = ["reversed"] }
"ui.menu.scroll" = { fg = "light-blue" }
"ui.popup" = { bg = "black" }
"ui.selection" = { bg = "gray" }
"ui.statusline" = { fg = "light-gray", bg = "gray" }
"ui.statusline.inactive" = { bg = "black" }
"ui.virtual" = { bg = "black" }
"ui.virtual.indent-guide" = { fg = "gray" }
"ui.virtual.whitespace" = {}
"ui.virtual.wrap" = { fg = "gray" }
"ui.virtual.inlay-hint" = { fg = "light-gray", modifiers = ["dim", "italic"] }
"ui.virtual.inlay-hint.parameter" = { fg = "yellow", modifiers = ["dim", "italic"] }
"ui.virtual.inlay-hint.type" = { fg = "blue", modifiers = ["dim", "italic"] }
"ui.window" = { fg = "gray", modifiers = ["dim"] }
"comment" = { fg = "light-gray", modifiers = ["italic", "dim"] }
"attribute" = "light-yellow"
"constant" = { fg = "light-yellow", modifiers = ["bold", "dim"] }
"constant.numeric" = "light-yellow"
"constant.character.escape" = "light-cyan"
"constructor" = "light-blue"
"function" = "light-blue"
"function.macro" = "light-red"
"function.builtin" = { fg = "light-blue", modifiers = ["bold"] }
"tag" = { fg = "light-magenta", modifiers = ["dim"] }
"type" = "blue"
"type.builtin" = { fg = "blue", modifiers = ["bold"] }
"type.enum.variant" = { fg = "light-magenta", modifiers = ["dim"] }
"string" = "light-green"
"special" = "light-red"
"variable" = "white"
"variable.parameter" = { fg = "light-yellow", modifiers = ["italic"] }
"variable.other.member" = "light-green"
"keyword" = "light-magenta"
"keyword.control.exception" = "light-red"
"keyword.directive" = { fg = "light-yellow", modifiers = ["bold"] }
"keyword.operator" = { fg = "light-blue", modifiers = ["bold"] }
"label" = "light-green"
"namespace" = { fg = "blue", modifiers = ["dim"] }
"markup.heading" = "light-blue"
"markup.list" = "light-red"
"markup.bold" = { fg = "light-cyan", modifiers = ["bold"] }
"markup.italic" = { fg = "light-blue", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "magenta", modifiers = ["dim"] }
"markup.link.text" = "light-magenta"
"markup.quote" = "light-cyan"
"markup.raw" = "light-green"
"diff.plus" = "light-green"
"diff.delta" = "light-yellow"
"diff.minus" = "light-red"
"diagnostic.hint" = { underline = { color = "gray", style = "curl" } }
"diagnostic.info" = { underline = { color = "light-cyan", style = "curl" } }
"diagnostic.warning" = { underline = { color = "light-yellow", style = "curl" } }
"diagnostic.error" = { underline = { color = "light-red", style = "curl" } }
"info" = "light-cyan"
"hint" = { fg = "light-gray", modifiers = ["dim"] }
"debug" = "white"
"warning" = "yellow"
"error" = "light-red"

@ -0,0 +1,85 @@
# Author: dgkf
# Modified from base16_terminal, Author: NNB <nnbnh@protonmail.com>
inherits = "term16_dark"
"ui.background.separator" = "light-gray"
"ui.cursor" = { fg = "gray", modifiers = ["reversed"] }
"ui.cursor.match" = { fg = "yellow", modifiers = ["reversed"] }
"ui.cursor.primary" = { fg = "black", modifiers = ["reversed"] }
"ui.cursor.secondary" = { fg = "gray", modifiers = ["reversed"] }
"ui.cursorline.primary" = { bg = "white" }
"ui.cursorline.secondary" = { bg = "white" }
"ui.cursorcolumn.primary" = { bg = "white" }
"ui.cursorcolumn.secondary" = { bg = "white" }
"ui.gutter" = { }
"ui.gutter.selected" = { bg = "white" }
"ui.linenr" = { fg = "gray", modifiers = ["dim"] }
"ui.linenr.selected" = { fg = "black", modifiers = ["bold"] }
"ui.menu" = { bg = "light-gray" }
"ui.menu.selected" = { fg = "white", bg = "gray", modifiers = ["bold"] }
"ui.menu.scroll" = { fg = "light-blue" }
"ui.help" = { }
"ui.text" = { }
"ui.text.focus" = { }
"ui.popup" = { bg = "white" }
"ui.selection" = { bg = "light-gray" }
"ui.statusline" = { bg = "white" }
"ui.statusline.inactive" = { fg = "gray", modifiers = ["underlined"] }
"ui.statusline.insert" = { fg = "white", bg = "blue" }
"ui.statusline.select" = { fg = "white", bg = "magenta" }
"ui.virtual" = { fg = "light-gray" }
"ui.virtual.indent-guide" = { fg = "light-gray", modifiers = ["dim"] }
"ui.virtual.ruler" = { bg = "white" }
"ui.virtual.wrap" = { fg = "light-gray" }
"ui.window" = { fg = "gray", modifiers = ["dim"] }
"comment" = { fg = "gray", modifiers = ["italic", "dim"] }
"attribute" = "yellow"
"constant" = { fg = "yellow", modifiers = ["bold"] }
"constant.numeric" = { fg = "yellow", modifiers = ["bold"] }
"constant.character.escape" = "blue"
"constructor" = "blue"
"function" = "blue"
"function.builtin" = { fg = "blue", modifiers = ["bold"] }
"tag" = { fg = "magenta", modifiers = ["dim"] }
"type" = "blue"
"type.builtin" = { fg = "blue", modifiers = ["bold"] }
"type.enum.variant" = { fg = "magenta", modifiers = ["dim"] }
"string" = "green"
"special" = "red"
"variable" = { fg = "black", modifiers = ["dim"] }
"variable.parameter" = { fg = "red", modifiers = ["italic", "dim"] }
"variable.other.member" = "green"
"keyword" = "magenta"
"keyword.control.exception" = "red"
"keyword.directive" = { fg = "yellow", modifiers = ["bold"] }
"keyword.operator" = { fg = "blue", modifiers = ["bold"] }
"label" = "red"
"namespace" = { fg = "blue", modifiers = ["dim"] }
"markup.heading" = { fg = "blue", modifiers = ["bold"] }
"markup.list" = "red"
"markup.bold" = { fg = "cyan", modifiers = ["bold"] }
"markup.italic" = { fg = "blue", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "magenta", modifiers = ["dim"] }
"markup.link.text" = { fg = "magenta", modifiers = ["bold"] }
"markup.quote" = "cyan"
"markup.raw" = "blue"
"diff.plus" = "green"
"diff.delta" = "yellow"
"diff.minus" = "red"
"diagnostic.hint" = { underline = { color = "cyan", style = "curl" } }
"diagnostic.info" = { underline = { color = "blue", style = "curl" } }
"diagnostic.warning" = { underline = { color = "yellow", style = "curl" } }
"diagnostic.error" = { underline = { color = "red", style = "curl" } }
"hint" = "cyan"
"info" = "blue"
"debug" = "light-yellow"
"warning" = "yellow"
"error" = "red"

@ -4,22 +4,20 @@
"comment" = { fg = "light-gray", modifiers = ["italic"] } "comment" = { fg = "light-gray", modifiers = ["italic"] }
"constant" = { fg = "yellow" } "constant" = { fg = "yellow" }
"constant.numeric" = { fg = "orange" } "constant.numeric" = { fg = "orange" }
"constant.builtin" = { fg = "orange" } "constant.builtin" = { fg = "yellow" }
"constant.builtin.boolean" = { fg = "yellow" } "constant.builtin.boolean" = { fg = "yellow" }
"constant.character.escape" = { fg = "orange" } "constant.character.escape" = { fg = "yellow" }
"constructor" = { fg = "blue" } "constructor" = { fg = "blue" }
"function" = { fg = "blue" } "function" = { fg = "blue" }
"function.builtin" = { fg = "blue" } "function.builtin" = { fg = "blue" }
"function.macro" = { fg = "purple" } "function.method" = { fg = "blue" }
"function.macro" = { fg = "blue" }
"keyword" = { fg = "purple" } "keyword" = { fg = "purple" }
"keyword.control" = { fg = "purple" }
"keyword.control.import" = { fg = "purple" }
"keyword.directive" = { fg = "purple" }
"label" = { fg = "ui-text" } "label" = { fg = "ui-text" }
"namespace" = { fg = "ui-text" } "namespace" = { fg = "ui-text" }
"operator" = { fg = "ui-text" } "operator" = { fg = "ui-text" }
"keyword.operator" = { fg = "purple" } "puncuation" = { fg = "ui-text" }
"special" = { fg = "blue" } "special" = { fg = "ui-text" }
"string" = { fg = "green" } "string" = { fg = "green" }
"type" = { fg = "cyan" } "type" = { fg = "cyan" }
"variable.builtin" = { fg = "orange" } "variable.builtin" = { fg = "orange" }
@ -28,41 +26,43 @@
"markup.heading" = { fg = "red" } "markup.heading" = { fg = "red" }
"markup.raw.inline" = { fg = "green" } "markup.raw.inline" = { fg = "green" }
"markup.bold" = { fg = "orange", modifiers = ["bold"] } "markup.bold" = { fg = "yellow", modifiers = ["bold"] }
"markup.italic" = { fg = "purple", modifiers = ["italic"] } "markup.italic" = { fg = "purple", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] } "markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.list" = { fg = "red" } "markup.list" = { fg = "red" }
"markup.quote" = { fg = "yellow" } "markup.quote" = { fg = "yellow" }
"markup.link.url" = { fg = "cyan", modifiers = ["underlined"]} "markup.link.url" = { fg = "cyan", modifiers = ["underlined"] }
"markup.link.text" = { fg = "purple" } "markup.link.text" = { fg = "purple" }
"diff.plus" = "green" "diff.plus" = "green"
"diff.delta" = "orange" "diff.delta" = "yellow"
"diff.minus" = "red" "diff.minus" = "red"
"diagnostic.info".underline = { color = "blue", style = "curl" } "diagnostic.info".underline = { color = "blue", style = "curl" }
"diagnostic.hint".underline = { color = "green", style = "curl" } "diagnostic.hint".underline = { color = "green", style = "curl" }
"diagnostic.warning".underline = { color = "yellow", style = "curl" } "diagnostic.warning".underline = { color = "yellow", style = "curl" }
"diagnostic.error".underline = { color = "red", style = "curl" } "diagnostic.error".underline = { color = "red", style = "curl" }
"info" = { fg = "blue", modifiers = ["bold"] } "info" = { fg = "blue", modifiers = ["bold"] }
"hint" = { fg = "green", modifiers = ["bold"] } "hint" = { fg = "green", modifiers = ["bold"] }
"warning" = { fg = "yellow", modifiers = ["bold"] } "warning" = { fg = "yellow", modifiers = ["bold"] }
"error" = { fg = "red", modifiers = ["bold"] } "error" = { fg = "red", modifiers = ["bold"] }
"ui.background" = { bg = "ui-text-reversed" } "ui.background" = { bg = "ui-text-reversed" }
"ui.gutter" = { bg = "gray" }
"ui.virtual" = { fg = "faint-gray" } "ui.virtual" = { fg = "faint-gray" }
"ui.virtual.indent-guide" = { fg = "faint-gray" } "ui.virtual.indent-guide" = { fg = "faint-gray" }
"ui.virtual.whitespace" = { fg = "light-gray" } "ui.virtual.whitespace" = { fg = "light-gray" }
"ui.virtual.ruler" = { bg = "gray" } "ui.virtual.ruler" = { bg = "gray" }
"ui.virtual.inlay-hint" = { fg = "light-gray" } "ui.virtual.inlay-hint" = { fg = "blue-gray", modifiers = ["bold"] }
"ui.cursor" = { fg = "white", modifiers = ["reversed"] } "ui.cursor" = { fg = "white", modifiers = ["reversed"] }
"ui.cursor.primary" = { fg = "white", modifiers = ["reversed"] } "ui.cursor.primary" = { fg = "white", modifiers = ["reversed"] }
"ui.cursor.match" = { fg = "blue", modifiers = ["underlined"]} "ui.cursor.match" = { fg = "blue", modifiers = ["underlined"] }
"ui.cursor.insert" = { fg = "dark-blue" }
"ui.selection" = { bg = "faint-gray" } "ui.selection" = { bg = "faint-gray" }
"ui.selection.primary" = { bg = "gray" } "ui.selection.primary" = { bg = "#293b5bff" }
"ui.cursorline.primary" = { bg = "light-black" } "ui.cursorline.primary" = { bg = "gray" }
"ui.highlight" = { bg = "gray" } "ui.highlight" = { bg = "gray" }
"ui.highlight.frameline" = { bg = "#97202a" } "ui.highlight.frameline" = { bg = "#97202a" }
@ -70,14 +70,14 @@
"ui.linenr" = { fg = "linenr" } "ui.linenr" = { fg = "linenr" }
"ui.linenr.selected" = { fg = "ui-text" } "ui.linenr.selected" = { fg = "ui-text" }
"ui.statusline" = { fg = "white", bg = "light-black" } "ui.statusline" = { fg = "white", bg = "gray" }
"ui.statusline.inactive" = { fg = "light-gray", bg = "light-black" } "ui.statusline.inactive" = { fg = "light-gray", bg = "black" }
"ui.statusline.normal" = { fg = "light-black", bg = "blue" } "ui.statusline.normal" = { fg = "black", bg = "blue" }
"ui.statusline.insert" = { fg = "light-black", bg = "green" } "ui.statusline.insert" = { fg = "black", bg = "green" }
"ui.statusline.select" = { fg = "light-black", bg = "purple" } "ui.statusline.select" = { fg = "black", bg = "purple" }
"ui.text" = { fg = "ui-text" } "ui.text" = { fg = "ui-text" }
"ui.text.focus" = { fg = "white", bg = "light-black", modifiers = ["bold"] } "ui.text.focus" = { fg = "white", bg = "gray", modifiers = ["bold"] }
"ui.help" = { fg = "white", bg = "gray" } "ui.help" = { fg = "white", bg = "gray" }
"ui.popup" = { bg = "gray" } "ui.popup" = { bg = "gray" }
@ -89,22 +89,21 @@
"ui.debug" = { fg = "red" } "ui.debug" = { fg = "red" }
[palette] [palette]
yellow = "#dfc184ff"
yellow = "#dac18c" orange = "#bf956aff"
blue = "#7ca8dd" blue = "#73ade9ff"
red = "#bd7476" blue-gray = "#5a6f89ff"
purple = "#9d74b9" red = "#d07277ff"
green = "#a0b783" purple = "#b477cfff"
orange = "#b4926e" green = "#a1c181ff"
cyan = "#7eb2be" cyan = "#6eb4bfff"
light-black = "#2e323a" gray = "#2f343ebf"
gray = "#363f4c" light-gray = "#5d636fff"
light-gray = "#5c606b"
faint-gray = "#3B4048" faint-gray = "#3B4048"
linenr = "#4B5263" linenr = "#5d636fff"
white = "#a8adb7" white = "#c8ccd4ff"
black = "#292c33" black = "#282c33ff"
# black and white are used for a lot of the UI text # black and white are used for a lot of the UI text
ui-text = "#a8adb7" #white ui-text = "#c8ccd4ff" #white
ui-text-reversed = "#292c33" #black ui-text-reversed = "#282c33ff" #black

@ -16,9 +16,13 @@ inherits = "zed_onedark"
"ui.cursor" = { fg = "dark-blue", modifiers = ["reversed"] } "ui.cursor" = { fg = "dark-blue", modifiers = ["reversed"] }
"ui.cursor.primary" = { fg = "dark-blue", modifiers = ["reversed"] } "ui.cursor.primary" = { fg = "dark-blue", modifiers = ["reversed"] }
"ui.cursor.insert" = { fg = "dark-blue" }
"ui.selection.primary" = { bg = "blue-gray" }
"ui.cursorline.primary" = { bg = "faint-gray" } "ui.cursorline.primary" = { bg = "faint-gray" }
"ui.virtual.inlay-hint" = { fg = "violet", modifiers = ["bold"] }
"ui.statusline" = { fg = "black", bg = "gray" } "ui.statusline" = { fg = "black", bg = "gray" }
"ui.statusline.inactive" = { fg = "white", bg = "light-black" } "ui.statusline.inactive" = { fg = "white", bg = "light-black" }
"ui.statusline.normal" = { fg = "white", bg = "blue" } "ui.statusline.normal" = { fg = "white", bg = "blue" }
@ -32,24 +36,26 @@ inherits = "zed_onedark"
"ui.window" = { fg = "dark-gray" } "ui.window" = { fg = "dark-gray" }
[palette] [palette]
yellow = "#dabb7e"
yellow = "#dac18c" red = "#d36151ff"
blue = "#5185b5" orange = "#d3604fff"
red = "#bd7476" blue = "#5b79e3ff"
dark-blue = "#607bdb" dark-blue = "#4a62db"
orange = "#ca7667" purple = "#a449abff"
purple = "#a160ac" violet = "#9294beff"
green = "#739d60" green = "#649f57ff"
gold = "#a8763c" gold = "#ad6e25ff"
cyan = "#4b80b2" cyan = "#3882b7ff"
light-black = "#2e323a" light-black = "#2e323a"
gray = "#dcdcdd" # gray = "#dcdcdd"
gray = "#eaeaed"
dark-gray = "#ebebec" dark-gray = "#ebebec"
light-gray = "#a6a6aa" light-gray = "#a2a3a7ff"
blue-gray = "#d9dcea"
faint-gray = "#efefef" faint-gray = "#efefef"
linenr = "#4B5263" linenr = "#b0b1b3"
black = "#404248" black = "#383a41ff"
white = "#fafafa" white = "#fafafaff"
ui-text = "#404248" ui-text = "#383a41ff"
ui-text-reversed = "#fafafa" ui-text-reversed = "#fafafaff"

@ -16,4 +16,4 @@ helix-term = { path = "../helix-term" }
helix-core = { path = "../helix-core" } helix-core = { path = "../helix-core" }
helix-view = { path = "../helix-view" } helix-view = { path = "../helix-view" }
helix-loader = { path = "../helix-loader" } helix-loader = { path = "../helix-loader" }
toml = "0.7" toml = "0.8"

Loading…
Cancel
Save