mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-25 10:56:19 +04:00
Merge branch 'helix-editor:master' into pull-diagnostics
This commit is contained in:
commit
b72ccfde7c
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@ -16,6 +16,7 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install stable toolchain
|
||||
uses: dtolnay/rust-toolchain@1.70
|
||||
|
||||
@ -107,6 +108,9 @@ jobs:
|
||||
- name: Validate queries
|
||||
run: cargo xtask query-check
|
||||
|
||||
- name: Validate themes
|
||||
run: cargo xtask theme-check
|
||||
|
||||
- name: Generate docs
|
||||
run: cargo xtask docgen
|
||||
|
||||
|
2
.github/workflows/cachix.yml
vendored
2
.github/workflows/cachix.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install nix
|
||||
uses: cachix/install-nix-action@V27
|
||||
uses: cachix/install-nix-action@v29
|
||||
|
||||
- name: Authenticate with Cachix
|
||||
uses: cachix/cachix-action@v15
|
||||
|
51
Cargo.lock
generated
51
Cargo.lock
generated
@ -68,9 +68,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.87"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8"
|
||||
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
@ -136,9 +136,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.1.18"
|
||||
version = "1.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476"
|
||||
checksum = "3bbb537bb4a30b90362caddba8f360c0a56bc13d3a5570028e7197204cb54a17"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
@ -1609,9 +1609,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.158"
|
||||
version = "0.2.159"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
|
||||
checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
@ -1753,9 +1753,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
version = "1.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "open"
|
||||
@ -1914,9 +1917,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.6"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
|
||||
checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@ -1926,9 +1929,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.7"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
|
||||
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@ -1950,9 +1953,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.4"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "ropey"
|
||||
@ -1972,9 +1975,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.36"
|
||||
version = "0.38.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36"
|
||||
checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
@ -2192,9 +2195,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.12.0"
|
||||
version = "3.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64"
|
||||
checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
@ -2225,18 +2228,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.63"
|
||||
version = "1.0.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
|
||||
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.63"
|
||||
version = "1.0.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
|
||||
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -2401,9 +2404,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.11.0"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
|
@ -37,8 +37,8 @@ # Features
|
||||
- Built-in language server support
|
||||
- Smart, incremental syntax highlighting and code editing via tree-sitter
|
||||
|
||||
It's a terminal-based editor first, but I'd like to explore a custom renderer
|
||||
(similar to Emacs) in wgpu or skulpin.
|
||||
Although it's primarily a terminal-based editor, I am interested in exploring
|
||||
a custom renderer (similar to Emacs) using wgpu or skulpin.
|
||||
|
||||
Note: Only certain languages have indentation definitions at the moment. Check
|
||||
`runtime/queries/<lang>/` for `indents.scm`.
|
||||
|
@ -19,6 +19,7 @@
|
||||
| cairo | ✓ | ✓ | ✓ | `cairo-language-server` |
|
||||
| capnp | ✓ | | ✓ | |
|
||||
| cel | ✓ | | | |
|
||||
| circom | ✓ | | | `circom-lsp` |
|
||||
| clojure | ✓ | | | `clojure-lsp` |
|
||||
| cmake | ✓ | ✓ | ✓ | `cmake-language-server` |
|
||||
| comment | ✓ | | | |
|
||||
@ -48,7 +49,7 @@
|
||||
| elvish | ✓ | | | `elvish` |
|
||||
| env | ✓ | ✓ | | |
|
||||
| erb | ✓ | | | |
|
||||
| erlang | ✓ | ✓ | | `erlang_ls` |
|
||||
| erlang | ✓ | ✓ | | `erlang_ls`, `elp` |
|
||||
| esdl | ✓ | | | |
|
||||
| fidl | ✓ | | | |
|
||||
| fish | ✓ | ✓ | ✓ | |
|
||||
@ -86,7 +87,7 @@
|
||||
| hocon | ✓ | ✓ | ✓ | |
|
||||
| hoon | ✓ | | | |
|
||||
| hosts | ✓ | | | |
|
||||
| html | ✓ | | | `vscode-html-language-server` |
|
||||
| html | ✓ | | | `vscode-html-language-server`, `superhtml` |
|
||||
| hurl | ✓ | ✓ | ✓ | |
|
||||
| hyprlang | ✓ | | ✓ | |
|
||||
| idris | | | | `idris2-lsp` |
|
||||
@ -163,7 +164,7 @@
|
||||
| protobuf | ✓ | ✓ | ✓ | `bufls`, `pb` |
|
||||
| prql | ✓ | | | |
|
||||
| purescript | ✓ | ✓ | | `purescript-language-server` |
|
||||
| python | ✓ | ✓ | ✓ | `pylsp` |
|
||||
| python | ✓ | ✓ | ✓ | `ruff`, `jedi-language-server`, `pylsp` |
|
||||
| qml | ✓ | | ✓ | `qmlls` |
|
||||
| r | ✓ | | | `R` |
|
||||
| racket | ✓ | | ✓ | `racket` |
|
||||
|
@ -72,7 +72,7 @@
|
||||
| `:sort` | Sort ranges in selection. |
|
||||
| `:rsort` | Sort ranges in selection in reverse order. |
|
||||
| `:reflow` | Hard-wrap the current selection of lines to a given width. |
|
||||
| `:tree-sitter-subtree`, `:ts-subtree` | Display tree sitter subtree under cursor, primarily for debugging queries. |
|
||||
| `:tree-sitter-subtree`, `:ts-subtree` | Display the smallest tree-sitter subtree that spans the primary selection, primarily for debugging queries. |
|
||||
| `:config-reload` | Refresh user config. |
|
||||
| `:config-open` | Open the user config.toml file. |
|
||||
| `:config-open-workspace` | Open the workspace config.toml file. |
|
||||
|
@ -145,6 +145,9 @@ ### Selection manipulation
|
||||
| `Alt-i`, `Alt-down` | Shrink syntax tree object selection (**TS**) | `shrink_selection` |
|
||||
| `Alt-p`, `Alt-left` | Select previous sibling node in syntax tree (**TS**) | `select_prev_sibling` |
|
||||
| `Alt-n`, `Alt-right` | Select next sibling node in syntax tree (**TS**) | `select_next_sibling` |
|
||||
| `Alt-a` | Select all sibling nodes in syntax tree (**TS**) | `select_all_siblings` |
|
||||
| `Alt-e` | Move to end of parent node in syntax tree (**TS**) | `move_parent_node_end` |
|
||||
| `Alt-b` | Move to start of parent node in syntax tree (**TS**) | `move_parent_node_start` |
|
||||
|
||||
### Search
|
||||
|
||||
|
@ -7,3 +7,27 @@ # Using Helix
|
||||
> 💡 Currently, not all functionality is fully documented, please refer to the
|
||||
> [key mappings](./keymap.md) list.
|
||||
|
||||
## Modes
|
||||
|
||||
Helix is a modal editor, meaning it has different modes for different tasks. The main modes are:
|
||||
|
||||
* [Normal mode](./keymap.md#normal-mode): For navigation and editing commands. This is the default mode.
|
||||
* [Insert mode](./keymap.md#insert-mode): For typing text directly into the document. Access by typing `i` in normal mode.
|
||||
* [Select/extend mode](./keymap.md#select--extend-mode): For making selections and performing operations on them. Access by typing `v` in normal mode.
|
||||
|
||||
## Buffers
|
||||
|
||||
Buffers are in-memory representations of files. You can have multiple buffers open at once. Use [pickers](./pickers.md) or commands like `:buffer-next` and `:buffer-previous` to open buffers or switch between them.
|
||||
|
||||
## Selection-first editing
|
||||
|
||||
Inspired by [Kakoune](http://kakoune.org/), Helix follows the `selection → action` model. This means that whatever you are going to act on (a word, a paragraph, a line, etc.) is selected first and the action itself (delete, change, yank, etc.) comes second. A cursor is simply a single width selection.
|
||||
|
||||
## Multiple selections
|
||||
|
||||
Also inspired by Kakoune, multiple selections are a core mode of interaction in Helix. For example, the standard way of replacing multiple instance of a word is to first select all instances (so there is one selection per instance) and then use the change action (`c`) to edit them all at the same time.
|
||||
|
||||
## Motions
|
||||
|
||||
Motions are commands that move the cursor or modify selections. They're used for navigation and text manipulation. Examples include `w` to move to the next word, or `f` to find a character. See the [Movement](./keymap.md#movement) section of the keymap for more motions.
|
||||
|
||||
|
@ -22,7 +22,7 @@ helix-loader = { path = "../helix-loader" }
|
||||
ropey = { version = "1.6.1", default-features = false, features = ["simd"] }
|
||||
smallvec = "1.13"
|
||||
smartstring = "1.0.1"
|
||||
unicode-segmentation = "1.11"
|
||||
unicode-segmentation = "1.12"
|
||||
# unicode-width is changing width definitions
|
||||
# that both break our logic and disagree with common
|
||||
# width definitions in terminals, we need to replace it.
|
||||
@ -32,7 +32,7 @@ unicode-width = "=0.1.12"
|
||||
unicode-general-category = "0.6"
|
||||
slotmap.workspace = true
|
||||
tree-sitter.workspace = true
|
||||
once_cell = "1.19"
|
||||
once_cell = "1.20"
|
||||
arc-swap = "1"
|
||||
regex = "1"
|
||||
bitflags = "2.6"
|
||||
|
@ -2694,6 +2694,8 @@ fn pretty_print_tree_impl<W: fmt::Write>(
|
||||
}
|
||||
|
||||
write!(fmt, "({}", node.kind())?;
|
||||
} else {
|
||||
write!(fmt, " \"{}\"", node.kind())?;
|
||||
}
|
||||
|
||||
// Handle children.
|
||||
@ -2952,7 +2954,7 @@ fn assert_pretty_print(
|
||||
#[test]
|
||||
fn test_pretty_print() {
|
||||
let source = r#"// Hello"#;
|
||||
assert_pretty_print("rust", source, "(line_comment)", 0, source.len());
|
||||
assert_pretty_print("rust", source, "(line_comment \"//\")", 0, source.len());
|
||||
|
||||
// A large tree should be indented with fields:
|
||||
let source = r#"fn main() {
|
||||
@ -2962,16 +2964,16 @@ fn test_pretty_print() {
|
||||
"rust",
|
||||
source,
|
||||
concat!(
|
||||
"(function_item\n",
|
||||
"(function_item \"fn\"\n",
|
||||
" name: (identifier)\n",
|
||||
" parameters: (parameters)\n",
|
||||
" body: (block\n",
|
||||
" parameters: (parameters \"(\" \")\")\n",
|
||||
" body: (block \"{\"\n",
|
||||
" (expression_statement\n",
|
||||
" (macro_invocation\n",
|
||||
" macro: (identifier)\n",
|
||||
" (token_tree\n",
|
||||
" (string_literal\n",
|
||||
" (string_content)))))))",
|
||||
" macro: (identifier) \"!\"\n",
|
||||
" (token_tree \"(\"\n",
|
||||
" (string_literal \"\"\"\n",
|
||||
" (string_content) \"\"\") \")\")) \";\") \"}\"))",
|
||||
),
|
||||
0,
|
||||
source.len(),
|
||||
@ -2983,7 +2985,7 @@ fn test_pretty_print() {
|
||||
|
||||
// Error nodes are printed as errors:
|
||||
let source = r#"}{"#;
|
||||
assert_pretty_print("rust", source, "(ERROR)", 0, source.len());
|
||||
assert_pretty_print("rust", source, "(ERROR \"}\" \"{\")", 0, source.len());
|
||||
|
||||
// Fields broken under unnamed nodes are determined correctly.
|
||||
// In the following source, `object` belongs to the `singleton_method`
|
||||
@ -2998,11 +3000,11 @@ fn test_pretty_print() {
|
||||
"ruby",
|
||||
source,
|
||||
concat!(
|
||||
"(singleton_method\n",
|
||||
" object: (self)\n",
|
||||
"(singleton_method \"def\"\n",
|
||||
" object: (self) \".\"\n",
|
||||
" name: (identifier)\n",
|
||||
" body: (body_statement\n",
|
||||
" (true)))"
|
||||
" (true)) \"end\")"
|
||||
),
|
||||
0,
|
||||
source.len(),
|
||||
|
@ -1,12 +1,18 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{
|
||||
fmt,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
/// A generic pointer to a file location.
|
||||
///
|
||||
/// Currently this type only supports paths to local files.
|
||||
///
|
||||
/// Cloning this type is cheap: the internal representation uses an Arc.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[non_exhaustive]
|
||||
pub enum Uri {
|
||||
File(PathBuf),
|
||||
File(Arc<Path>),
|
||||
}
|
||||
|
||||
impl Uri {
|
||||
@ -23,26 +29,18 @@ pub fn as_path(&self) -> Option<&Path> {
|
||||
Self::File(path) => Some(path),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_path_buf(self) -> Option<PathBuf> {
|
||||
match self {
|
||||
Self::File(path) => Some(path),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PathBuf> for Uri {
|
||||
fn from(path: PathBuf) -> Self {
|
||||
Self::File(path)
|
||||
Self::File(path.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Uri> for PathBuf {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(uri: Uri) -> Result<Self, Self::Error> {
|
||||
match uri {
|
||||
Uri::File(path) => Ok(path),
|
||||
impl fmt::Display for Uri {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::File(path) => write!(f, "{}", path.display()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -59,11 +57,16 @@ pub enum UrlConversionErrorKind {
|
||||
UnableToConvert,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for UrlConversionError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
impl fmt::Display for UrlConversionError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.kind {
|
||||
UrlConversionErrorKind::UnsupportedScheme => {
|
||||
write!(f, "unsupported scheme in URL: {}", self.source.scheme())
|
||||
write!(
|
||||
f,
|
||||
"unsupported scheme '{}' in URL {}",
|
||||
self.source.scheme(),
|
||||
self.source
|
||||
)
|
||||
}
|
||||
UrlConversionErrorKind::UnableToConvert => {
|
||||
write!(f, "unable to convert URL to file path: {}", self.source)
|
||||
@ -77,7 +80,7 @@ impl std::error::Error for UrlConversionError {}
|
||||
fn convert_url_to_uri(url: &url::Url) -> Result<Uri, UrlConversionErrorKind> {
|
||||
if url.scheme() == "file" {
|
||||
url.to_file_path()
|
||||
.map(|path| Uri::File(helix_stdx::path::normalize(path)))
|
||||
.map(|path| Uri::File(helix_stdx::path::normalize(path).into()))
|
||||
.map_err(|_| UrlConversionErrorKind::UnableToConvert)
|
||||
} else {
|
||||
Err(UrlConversionErrorKind::UnsupportedScheme)
|
||||
|
@ -19,7 +19,7 @@ tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync", "p
|
||||
# setup new events on initialization, hardware-lock-elision hugely benefits this case
|
||||
# as it essentially makes the lock entirely free as long as there is no writes
|
||||
parking_lot = { version = "0.12", features = ["hardware-lock-elision"] }
|
||||
once_cell = "1.18"
|
||||
once_cell = "1.20"
|
||||
|
||||
anyhow = "1"
|
||||
log = "0.4"
|
||||
|
@ -22,7 +22,7 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
toml = "0.8"
|
||||
etcetera = "0.8"
|
||||
tree-sitter.workspace = true
|
||||
once_cell = "1.19"
|
||||
once_cell = "1.20"
|
||||
log = "0.4"
|
||||
|
||||
# TODO: these two should be on !wasm32 only
|
||||
@ -30,7 +30,7 @@ log = "0.4"
|
||||
# cloning/compiling tree-sitter grammars
|
||||
cc = { version = "1" }
|
||||
threadpool = { version = "1.0" }
|
||||
tempfile = "3.12.0"
|
||||
tempfile = "3.13.0"
|
||||
dunce = "1.0.5"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
|
@ -225,7 +225,7 @@ fn get_name(v: &Value) -> Option<&str> {
|
||||
/// Used as a ceiling dir for LSP root resolution, the filepicker and potentially as a future filewatching root
|
||||
///
|
||||
/// This function starts searching the FS upward from the CWD
|
||||
/// and returns the first directory that contains either `.git`, `.svn` or `.helix`.
|
||||
/// and returns the first directory that contains either `.git`, `.svn`, `.jj` or `.helix`.
|
||||
/// If no workspace was found returns (CWD, true).
|
||||
/// Otherwise (workspace, false) is returned
|
||||
pub fn find_workspace() -> (PathBuf, bool) {
|
||||
@ -233,6 +233,7 @@ pub fn find_workspace() -> (PathBuf, bool) {
|
||||
for ancestor in current_dir.ancestors() {
|
||||
if ancestor.join(".git").exists()
|
||||
|| ancestor.join(".svn").exists()
|
||||
|| ancestor.join(".jj").exists()
|
||||
|| ancestor.join(".helix").exists()
|
||||
{
|
||||
return (ancestor.to_owned(), false);
|
||||
|
@ -26,4 +26,4 @@ windows-sys = { version = "0.59", features = ["Win32_Foundation", "Win32_Securit
|
||||
rustix = { version = "0.38", features = ["fs"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.12"
|
||||
tempfile = "3.13"
|
||||
|
@ -51,7 +51,7 @@ fn starts_with(self, text: &str) -> bool {
|
||||
if len < text.len() {
|
||||
return false;
|
||||
}
|
||||
self.get_byte_slice(..len - text.len())
|
||||
self.get_byte_slice(..text.len())
|
||||
.map_or(false, |start| start == text)
|
||||
}
|
||||
|
||||
@ -137,4 +137,14 @@ fn next_char_at_byte() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn starts_with() {
|
||||
assert!(RopeSlice::from("asdf").starts_with("a"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ends_with() {
|
||||
assert!(RopeSlice::from("asdf").ends_with("f"));
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ helix-vcs = { path = "../helix-vcs" }
|
||||
helix-loader = { path = "../helix-loader" }
|
||||
|
||||
anyhow = "1"
|
||||
once_cell = "1.19"
|
||||
once_cell = "1.20"
|
||||
|
||||
tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
|
||||
tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] }
|
||||
@ -74,7 +74,7 @@ grep-searcher = "0.1.14"
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100
|
||||
signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
|
||||
libc = "0.2.158"
|
||||
libc = "0.2.159"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
crossterm = { version = "0.28", features = ["event-stream", "use-dev-tty", "libc"] }
|
||||
@ -85,5 +85,5 @@ helix-loader = { path = "../helix-loader" }
|
||||
[dev-dependencies]
|
||||
smallvec = "1.13"
|
||||
indoc = "2.0.5"
|
||||
tempfile = "3.12.0"
|
||||
tempfile = "3.13.0"
|
||||
same-file = "1.0.1"
|
||||
|
@ -770,7 +770,15 @@ macro_rules! language_server {
|
||||
);
|
||||
}
|
||||
Notification::ShowMessage(params) => {
|
||||
log::warn!("unhandled window/showMessage: {:?}", params);
|
||||
if self.config.load().editor.lsp.display_messages {
|
||||
match params.typ {
|
||||
lsp::MessageType::ERROR => self.editor.set_error(params.message),
|
||||
lsp::MessageType::WARNING => {
|
||||
self.editor.set_warning(params.message)
|
||||
}
|
||||
_ => self.editor.set_status(params.message),
|
||||
}
|
||||
}
|
||||
}
|
||||
Notification::LogMessage(params) => {
|
||||
log::info!("window/logMessage: {:?}", params);
|
||||
@ -854,7 +862,7 @@ macro_rules! language_server {
|
||||
self.lsp_progress.update(server_id, token, work);
|
||||
}
|
||||
|
||||
if self.config.load().editor.lsp.display_messages {
|
||||
if self.config.load().editor.lsp.display_progress_messages {
|
||||
self.editor.set_status(status);
|
||||
}
|
||||
}
|
||||
|
@ -4630,6 +4630,14 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
|
||||
let text = doc.text();
|
||||
let slice = text.slice(..);
|
||||
|
||||
let comment_tokens = doc
|
||||
.language_config()
|
||||
.and_then(|config| config.comment_tokens.as_deref())
|
||||
.unwrap_or(&[]);
|
||||
// Sort by length to handle Rust's /// vs //
|
||||
let mut comment_tokens: Vec<&str> = comment_tokens.iter().map(|x| x.as_str()).collect();
|
||||
comment_tokens.sort_unstable_by_key(|x| std::cmp::Reverse(x.len()));
|
||||
|
||||
let mut changes = Vec::new();
|
||||
|
||||
for selection in doc.selection(view.id) {
|
||||
@ -4641,10 +4649,31 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
|
||||
|
||||
changes.reserve(lines.len());
|
||||
|
||||
let first_line_idx = slice.line_to_char(start);
|
||||
let first_line_idx = skip_while(slice, first_line_idx, |ch| matches!(ch, ' ' | 't'))
|
||||
.unwrap_or(first_line_idx);
|
||||
let first_line = slice.slice(first_line_idx..);
|
||||
let mut current_comment_token = comment_tokens
|
||||
.iter()
|
||||
.find(|token| first_line.starts_with(token));
|
||||
|
||||
for line in lines {
|
||||
let start = line_end_char_index(&slice, line);
|
||||
let mut end = text.line_to_char(line + 1);
|
||||
end = skip_while(slice, end, |ch| matches!(ch, ' ' | '\t')).unwrap_or(end);
|
||||
let slice_from_end = slice.slice(end..);
|
||||
if let Some(token) = comment_tokens
|
||||
.iter()
|
||||
.find(|token| slice_from_end.starts_with(token))
|
||||
{
|
||||
if Some(token) == current_comment_token {
|
||||
end += token.chars().count();
|
||||
end = skip_while(slice, end, |ch| matches!(ch, ' ' | '\t')).unwrap_or(end);
|
||||
} else {
|
||||
// update current token, but don't delete this one.
|
||||
current_comment_token = Some(token);
|
||||
}
|
||||
}
|
||||
|
||||
let separator = if end == line_end_char_index(&slice, line + 1) {
|
||||
// the joining line contains only space-characters => don't include a whitespace when joining
|
||||
|
@ -34,7 +34,7 @@
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::{BTreeMap, HashSet},
|
||||
fmt::{Display, Write},
|
||||
fmt::Display,
|
||||
future::Future,
|
||||
path::Path,
|
||||
};
|
||||
@ -61,10 +61,31 @@ macro_rules! language_server_with_feature {
|
||||
}};
|
||||
}
|
||||
|
||||
/// A wrapper around `lsp::Location` that swaps out the LSP URI for `helix_core::Uri`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct Location {
|
||||
uri: Uri,
|
||||
range: lsp::Range,
|
||||
}
|
||||
|
||||
fn lsp_location_to_location(location: lsp::Location) -> Option<Location> {
|
||||
let uri = match location.uri.try_into() {
|
||||
Ok(uri) => uri,
|
||||
Err(err) => {
|
||||
log::warn!("discarding invalid or unsupported URI: {err}");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
Some(Location {
|
||||
uri,
|
||||
range: location.range,
|
||||
})
|
||||
}
|
||||
|
||||
struct SymbolInformationItem {
|
||||
location: Location,
|
||||
symbol: lsp::SymbolInformation,
|
||||
offset_encoding: OffsetEncoding,
|
||||
uri: Uri,
|
||||
}
|
||||
|
||||
struct DiagnosticStyles {
|
||||
@ -75,35 +96,35 @@ struct DiagnosticStyles {
|
||||
}
|
||||
|
||||
struct PickerDiagnostic {
|
||||
uri: Uri,
|
||||
location: Location,
|
||||
diag: lsp::Diagnostic,
|
||||
offset_encoding: OffsetEncoding,
|
||||
}
|
||||
|
||||
fn uri_to_file_location<'a>(uri: &'a Uri, range: &lsp::Range) -> Option<FileLocation<'a>> {
|
||||
let path = uri.as_path()?;
|
||||
let line = Some((range.start.line as usize, range.end.line as usize));
|
||||
fn location_to_file_location(location: &Location) -> Option<FileLocation> {
|
||||
let path = location.uri.as_path()?;
|
||||
let line = Some((
|
||||
location.range.start.line as usize,
|
||||
location.range.end.line as usize,
|
||||
));
|
||||
Some((path.into(), line))
|
||||
}
|
||||
|
||||
fn jump_to_location(
|
||||
editor: &mut Editor,
|
||||
location: &lsp::Location,
|
||||
location: &Location,
|
||||
offset_encoding: OffsetEncoding,
|
||||
action: Action,
|
||||
) {
|
||||
let (view, doc) = current!(editor);
|
||||
push_jump(view, doc);
|
||||
|
||||
let path = match location.uri.to_file_path() {
|
||||
Ok(path) => path,
|
||||
Err(_) => {
|
||||
let err = format!("unable to convert URI to filepath: {}", location.uri);
|
||||
let Some(path) = location.uri.as_path() else {
|
||||
let err = format!("unable to convert URI to filepath: {:?}", location.uri);
|
||||
editor.set_error(err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
jump_to_position(editor, &path, location.range, offset_encoding, action);
|
||||
jump_to_position(editor, path, location.range, offset_encoding, action);
|
||||
}
|
||||
|
||||
fn jump_to_position(
|
||||
@ -196,7 +217,10 @@ fn diag_picker(
|
||||
for (diag, ls) in diags {
|
||||
if let Some(ls) = cx.editor.language_server_by_id(ls) {
|
||||
flat_diag.push(PickerDiagnostic {
|
||||
location: Location {
|
||||
uri: uri.clone(),
|
||||
range: diag.range,
|
||||
},
|
||||
diag,
|
||||
offset_encoding: ls.offset_encoding(),
|
||||
});
|
||||
@ -243,7 +267,7 @@ fn diag_picker(
|
||||
// between message code and message
|
||||
2,
|
||||
ui::PickerColumn::new("path", |item: &PickerDiagnostic, _| {
|
||||
if let Some(path) = item.uri.as_path() {
|
||||
if let Some(path) = item.location.uri.as_path() {
|
||||
path::get_truncated_path(path)
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
@ -261,26 +285,14 @@ fn diag_picker(
|
||||
primary_column,
|
||||
flat_diag,
|
||||
styles,
|
||||
move |cx,
|
||||
PickerDiagnostic {
|
||||
uri,
|
||||
diag,
|
||||
offset_encoding,
|
||||
},
|
||||
action| {
|
||||
let Some(path) = uri.as_path() else {
|
||||
return;
|
||||
};
|
||||
jump_to_position(cx.editor, path, diag.range, *offset_encoding, action);
|
||||
move |cx, diag, action| {
|
||||
jump_to_location(cx.editor, &diag.location, diag.offset_encoding, action);
|
||||
let (view, doc) = current!(cx.editor);
|
||||
view.diagnostics_handler
|
||||
.immediately_show_diagnostic(doc, view.id);
|
||||
},
|
||||
)
|
||||
.with_preview(move |_editor, PickerDiagnostic { uri, diag, .. }| {
|
||||
let line = Some((diag.range.start.line as usize, diag.range.end.line as usize));
|
||||
Some((uri.as_path()?.into(), line))
|
||||
})
|
||||
.with_preview(move |_editor, diag| location_to_file_location(&diag.location))
|
||||
.truncate_start(false)
|
||||
}
|
||||
|
||||
@ -303,7 +315,10 @@ fn nested_to_flat(
|
||||
container_name: None,
|
||||
},
|
||||
offset_encoding,
|
||||
location: Location {
|
||||
uri: uri.clone(),
|
||||
range: symbol.selection_range,
|
||||
},
|
||||
});
|
||||
for child in symbol.children.into_iter().flatten() {
|
||||
nested_to_flat(list, file, uri, child, offset_encoding);
|
||||
@ -337,7 +352,10 @@ fn nested_to_flat(
|
||||
lsp::DocumentSymbolResponse::Flat(symbols) => symbols
|
||||
.into_iter()
|
||||
.map(|symbol| SymbolInformationItem {
|
||||
location: Location {
|
||||
uri: doc_uri.clone(),
|
||||
range: symbol.location.range,
|
||||
},
|
||||
symbol,
|
||||
offset_encoding,
|
||||
})
|
||||
@ -392,17 +410,10 @@ fn nested_to_flat(
|
||||
symbols,
|
||||
(),
|
||||
move |cx, item, action| {
|
||||
jump_to_location(
|
||||
cx.editor,
|
||||
&item.symbol.location,
|
||||
item.offset_encoding,
|
||||
action,
|
||||
);
|
||||
jump_to_location(cx.editor, &item.location, item.offset_encoding, action);
|
||||
},
|
||||
)
|
||||
.with_preview(move |_editor, item| {
|
||||
uri_to_file_location(&item.uri, &item.symbol.location.range)
|
||||
})
|
||||
.with_preview(move |_editor, item| location_to_file_location(&item.location))
|
||||
.truncate_start(false);
|
||||
|
||||
compositor.push(Box::new(overlaid(picker)))
|
||||
@ -453,8 +464,11 @@ pub fn workspace_symbol_picker(cx: &mut Context) {
|
||||
}
|
||||
};
|
||||
Some(SymbolInformationItem {
|
||||
symbol,
|
||||
location: Location {
|
||||
uri,
|
||||
range: symbol.location.range,
|
||||
},
|
||||
symbol,
|
||||
offset_encoding,
|
||||
})
|
||||
})
|
||||
@ -490,7 +504,7 @@ pub fn workspace_symbol_picker(cx: &mut Context) {
|
||||
})
|
||||
.without_filtering(),
|
||||
ui::PickerColumn::new("path", |item: &SymbolInformationItem, _| {
|
||||
if let Some(path) = item.uri.as_path() {
|
||||
if let Some(path) = item.location.uri.as_path() {
|
||||
path::get_relative_path(path)
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
@ -507,15 +521,10 @@ pub fn workspace_symbol_picker(cx: &mut Context) {
|
||||
[],
|
||||
(),
|
||||
move |cx, item, action| {
|
||||
jump_to_location(
|
||||
cx.editor,
|
||||
&item.symbol.location,
|
||||
item.offset_encoding,
|
||||
action,
|
||||
);
|
||||
jump_to_location(cx.editor, &item.location, item.offset_encoding, action);
|
||||
},
|
||||
)
|
||||
.with_preview(|_editor, item| uri_to_file_location(&item.uri, &item.symbol.location.range))
|
||||
.with_preview(|_editor, item| location_to_file_location(&item.location))
|
||||
.with_dynamic_query(get_symbols, None)
|
||||
.truncate_start(false);
|
||||
|
||||
@ -847,7 +856,7 @@ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fn goto_impl(
|
||||
editor: &mut Editor,
|
||||
compositor: &mut Compositor,
|
||||
locations: Vec<lsp::Location>,
|
||||
locations: Vec<Location>,
|
||||
offset_encoding: OffsetEncoding,
|
||||
) {
|
||||
let cwdir = helix_stdx::env::current_working_dir();
|
||||
@ -860,80 +869,41 @@ fn goto_impl(
|
||||
_locations => {
|
||||
let columns = [ui::PickerColumn::new(
|
||||
"location",
|
||||
|item: &lsp::Location, cwdir: &std::path::PathBuf| {
|
||||
// The preallocation here will overallocate a few characters since it will account for the
|
||||
// URL's scheme, which is not used most of the time since that scheme will be "file://".
|
||||
// Those extra chars will be used to avoid allocating when writing the line number (in the
|
||||
// common case where it has 5 digits or less, which should be enough for a cast majority
|
||||
// of usages).
|
||||
let mut res = String::with_capacity(item.uri.as_str().len());
|
||||
|
||||
if item.uri.scheme() == "file" {
|
||||
// With the preallocation above and UTF-8 paths already, this closure will do one (1)
|
||||
// allocation, for `to_file_path`, else there will be two (2), with `to_string_lossy`.
|
||||
if let Ok(path) = item.uri.to_file_path() {
|
||||
// We don't convert to a `helix_core::Uri` here because we've already checked the scheme.
|
||||
// This path won't be normalized but it's only used for display.
|
||||
res.push_str(
|
||||
&path.strip_prefix(cwdir).unwrap_or(&path).to_string_lossy(),
|
||||
);
|
||||
}
|
||||
|item: &Location, cwdir: &std::path::PathBuf| {
|
||||
let path = if let Some(path) = item.uri.as_path() {
|
||||
path.strip_prefix(cwdir).unwrap_or(path).to_string_lossy()
|
||||
} else {
|
||||
// Never allocates since we declared the string with this capacity already.
|
||||
res.push_str(item.uri.as_str());
|
||||
}
|
||||
item.uri.to_string().into()
|
||||
};
|
||||
|
||||
// Most commonly, this will not allocate, especially on Unix systems where the root prefix
|
||||
// is a simple `/` and not `C:\` (with whatever drive letter)
|
||||
write!(&mut res, ":{}", item.range.start.line + 1)
|
||||
.expect("Will only failed if allocating fail");
|
||||
res.into()
|
||||
format!("{path}:{}", item.range.start.line + 1).into()
|
||||
},
|
||||
)];
|
||||
|
||||
let picker = Picker::new(columns, 0, locations, cwdir, move |cx, location, action| {
|
||||
jump_to_location(cx.editor, location, offset_encoding, action)
|
||||
})
|
||||
.with_preview(move |_editor, location| {
|
||||
use crate::ui::picker::PathOrId;
|
||||
|
||||
let lines = Some((
|
||||
location.range.start.line as usize,
|
||||
location.range.end.line as usize,
|
||||
));
|
||||
|
||||
// TODO: we should avoid allocating by doing the Uri conversion ahead of time.
|
||||
//
|
||||
// To do this, introduce a `Location` type in `helix-core` that reuses the core
|
||||
// `Uri` type instead of the LSP `Url` type and replaces the LSP `Range` type.
|
||||
// Refactor the callers of `goto_impl` to pass iterators that translate the
|
||||
// LSP location type to the custom one in core, or have them collect and pass
|
||||
// `Vec<Location>`s. Replace the `uri_to_file_location` function with
|
||||
// `location_to_file_location` that takes only `&helix_core::Location` as
|
||||
// parameters.
|
||||
//
|
||||
// By doing this we can also eliminate the duplicated URI info in the
|
||||
// `SymbolInformationItem` type and introduce a custom Symbol type in `helix-core`
|
||||
// which will be reused in the future for tree-sitter based symbol pickers.
|
||||
let path = Uri::try_from(&location.uri).ok()?.as_path_buf()?;
|
||||
#[allow(deprecated)]
|
||||
Some((PathOrId::from_path_buf(path), lines))
|
||||
});
|
||||
.with_preview(move |_editor, location| location_to_file_location(location));
|
||||
compositor.push(Box::new(overlaid(picker)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn to_locations(definitions: Option<lsp::GotoDefinitionResponse>) -> Vec<lsp::Location> {
|
||||
fn to_locations(definitions: Option<lsp::GotoDefinitionResponse>) -> Vec<Location> {
|
||||
match definitions {
|
||||
Some(lsp::GotoDefinitionResponse::Scalar(location)) => vec![location],
|
||||
Some(lsp::GotoDefinitionResponse::Array(locations)) => locations,
|
||||
Some(lsp::GotoDefinitionResponse::Scalar(location)) => {
|
||||
lsp_location_to_location(location).into_iter().collect()
|
||||
}
|
||||
Some(lsp::GotoDefinitionResponse::Array(locations)) => locations
|
||||
.into_iter()
|
||||
.flat_map(lsp_location_to_location)
|
||||
.collect(),
|
||||
Some(lsp::GotoDefinitionResponse::Link(locations)) => locations
|
||||
.into_iter()
|
||||
.map(|location_link| lsp::Location {
|
||||
uri: location_link.target_uri,
|
||||
range: location_link.target_range,
|
||||
.map(|location_link| {
|
||||
lsp::Location::new(location_link.target_uri, location_link.target_range)
|
||||
})
|
||||
.flat_map(lsp_location_to_location)
|
||||
.collect(),
|
||||
None => Vec::new(),
|
||||
}
|
||||
@ -1018,7 +988,11 @@ pub fn goto_reference(cx: &mut Context) {
|
||||
cx.callback(
|
||||
future,
|
||||
move |editor, compositor, response: Option<Vec<lsp::Location>>| {
|
||||
let items = response.unwrap_or_default();
|
||||
let items: Vec<Location> = response
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.flat_map(lsp_location_to_location)
|
||||
.collect();
|
||||
if items.is_empty() {
|
||||
editor.set_error("No references found.");
|
||||
} else {
|
||||
|
@ -3032,7 +3032,7 @@ fn read(cx: &mut compositor::Context, args: &[Cow<str>], event: PromptEvent) ->
|
||||
TypableCommand {
|
||||
name: "tree-sitter-subtree",
|
||||
aliases: &["ts-subtree"],
|
||||
doc: "Display tree sitter subtree under cursor, primarily for debugging queries.",
|
||||
doc: "Display the smallest tree-sitter subtree that spans the primary selection, primarily for debugging queries.",
|
||||
fun: tree_sitter_subtree,
|
||||
signature: CommandSignature::none(),
|
||||
},
|
||||
|
@ -433,7 +433,7 @@ pub fn draw_grapheme(
|
||||
Grapheme::Newline => &self.newline,
|
||||
};
|
||||
|
||||
let in_bounds = self.column_in_bounds(position.col + width - 1);
|
||||
let in_bounds = self.column_in_bounds(position.col, width);
|
||||
|
||||
if in_bounds {
|
||||
self.surface.set_string(
|
||||
@ -452,7 +452,6 @@ pub fn draw_grapheme(
|
||||
);
|
||||
self.surface.set_style(rect, style);
|
||||
}
|
||||
|
||||
if *is_in_indent_area && !is_whitespace {
|
||||
*last_indent_level = position.col;
|
||||
*is_in_indent_area = false;
|
||||
@ -461,8 +460,8 @@ pub fn draw_grapheme(
|
||||
width
|
||||
}
|
||||
|
||||
pub fn column_in_bounds(&self, colum: usize) -> bool {
|
||||
self.offset.col <= colum && colum < self.viewport.width as usize + self.offset.col
|
||||
pub fn column_in_bounds(&self, colum: usize, width: usize) -> bool {
|
||||
self.offset.col <= colum && colum + width <= self.offset.col + self.viewport.width as usize
|
||||
}
|
||||
|
||||
/// Overlay indentation guides ontop of a rendered line
|
||||
|
@ -96,7 +96,10 @@ fn handle_event(&mut self, event: &Event, _cx: &mut Context) -> EventResult {
|
||||
fn render(&mut self, area: Rect, surface: &mut Buffer, cx: &mut Context) {
|
||||
let margin = Margin::horizontal(1);
|
||||
|
||||
let signature = &self.signatures[self.active_signature];
|
||||
let signature = self
|
||||
.signatures
|
||||
.get(self.active_signature)
|
||||
.unwrap_or_else(|| &self.signatures[0]);
|
||||
|
||||
let active_param_span = signature.active_param_range.map(|(start, end)| {
|
||||
vec![(
|
||||
@ -108,9 +111,13 @@ fn render(&mut self, area: Rect, surface: &mut Buffer, cx: &mut Context) {
|
||||
)]
|
||||
});
|
||||
|
||||
let sig = &self.signatures[self.active_signature];
|
||||
let signature = self
|
||||
.signatures
|
||||
.get(self.active_signature)
|
||||
.unwrap_or_else(|| &self.signatures[0]);
|
||||
|
||||
let sig_text = crate::ui::markdown::highlighted_code_block(
|
||||
sig.signature.as_str(),
|
||||
signature.signature.as_str(),
|
||||
&self.language,
|
||||
Some(&cx.editor.theme),
|
||||
Arc::clone(&self.config_loader),
|
||||
@ -130,7 +137,7 @@ fn render(&mut self, area: Rect, surface: &mut Buffer, cx: &mut Context) {
|
||||
let sig_text_para = Paragraph::new(&sig_text).wrap(Wrap { trim: false });
|
||||
sig_text_para.render(sig_text_area, surface);
|
||||
|
||||
if sig.signature_doc.is_none() {
|
||||
if signature.signature_doc.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -142,7 +149,7 @@ fn render(&mut self, area: Rect, surface: &mut Buffer, cx: &mut Context) {
|
||||
}
|
||||
}
|
||||
|
||||
let sig_doc = match &sig.signature_doc {
|
||||
let sig_doc = match &signature.signature_doc {
|
||||
None => return,
|
||||
Some(doc) => Markdown::new(doc.clone(), Arc::clone(&self.config_loader)),
|
||||
};
|
||||
@ -160,12 +167,15 @@ fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
|
||||
const PADDING: u16 = 2;
|
||||
const SEPARATOR_HEIGHT: u16 = 1;
|
||||
|
||||
let sig = &self.signatures[self.active_signature];
|
||||
let signature = self
|
||||
.signatures
|
||||
.get(self.active_signature)
|
||||
.unwrap_or_else(|| &self.signatures[0]);
|
||||
|
||||
let max_text_width = viewport.0.saturating_sub(PADDING).clamp(10, 120);
|
||||
|
||||
let signature_text = crate::ui::markdown::highlighted_code_block(
|
||||
sig.signature.as_str(),
|
||||
signature.signature.as_str(),
|
||||
&self.language,
|
||||
None,
|
||||
Arc::clone(&self.config_loader),
|
||||
@ -174,7 +184,7 @@ fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
|
||||
let (sig_width, sig_height) =
|
||||
crate::ui::text::required_size(&signature_text, max_text_width);
|
||||
|
||||
let (width, height) = match sig.signature_doc {
|
||||
let (width, height) = match signature.signature_doc {
|
||||
Some(ref doc) => {
|
||||
let doc_md = Markdown::new(doc.clone(), Arc::clone(&self.config_loader));
|
||||
let doc_text = doc_md.parse(None);
|
||||
|
@ -32,7 +32,7 @@
|
||||
borrow::Cow,
|
||||
collections::HashMap,
|
||||
io::Read,
|
||||
path::{Path, PathBuf},
|
||||
path::Path,
|
||||
sync::{
|
||||
atomic::{self, AtomicUsize},
|
||||
Arc,
|
||||
@ -63,26 +63,12 @@
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
pub enum PathOrId<'a> {
|
||||
Id(DocumentId),
|
||||
// See [PathOrId::from_path_buf]: this will eventually become `Path(&Path)`.
|
||||
Path(Cow<'a, Path>),
|
||||
}
|
||||
|
||||
impl<'a> PathOrId<'a> {
|
||||
/// Creates a [PathOrId] from a PathBuf
|
||||
///
|
||||
/// # Deprecated
|
||||
/// The owned version of PathOrId will be removed in a future refactor
|
||||
/// and replaced with `&'a Path`. See the caller of this function for
|
||||
/// more details on its removal.
|
||||
#[deprecated]
|
||||
pub fn from_path_buf(path_buf: PathBuf) -> Self {
|
||||
Self::Path(Cow::Owned(path_buf))
|
||||
}
|
||||
Path(&'a Path),
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Path> for PathOrId<'a> {
|
||||
fn from(path: &'a Path) -> Self {
|
||||
Self::Path(Cow::Borrowed(path))
|
||||
Self::Path(path)
|
||||
}
|
||||
}
|
||||
|
||||
@ -581,7 +567,6 @@ fn get_preview<'picker, 'editor>(
|
||||
|
||||
match path_or_id {
|
||||
PathOrId::Path(path) => {
|
||||
let path = path.as_ref();
|
||||
if let Some(doc) = editor.document_by_path(path) {
|
||||
return Some((Preview::EditorDocument(doc), range));
|
||||
}
|
||||
|
@ -164,7 +164,7 @@ fn decorate_grapheme(
|
||||
renderer: &mut TextRenderer,
|
||||
grapheme: &FormattedGrapheme,
|
||||
) -> usize {
|
||||
if renderer.column_in_bounds(grapheme.visual_pos.col)
|
||||
if renderer.column_in_bounds(grapheme.visual_pos.col, grapheme.width())
|
||||
&& renderer.offset.row < grapheme.visual_pos.row
|
||||
{
|
||||
let position = grapheme.visual_pos - renderer.offset;
|
||||
|
@ -98,7 +98,7 @@ fn draw_decoration_at(&mut self, g: &'static str, severity: Severity, col: u16,
|
||||
fn draw_eol_diagnostic(&mut self, diag: &Diagnostic, row: u16, col: usize) -> u16 {
|
||||
let style = self.styles.severity_style(diag.severity());
|
||||
let width = self.renderer.viewport.width;
|
||||
if !self.renderer.column_in_bounds(col + 1) {
|
||||
if !self.renderer.column_in_bounds(col + 1, 1) {
|
||||
return 0;
|
||||
}
|
||||
let col = (col - self.renderer.offset.col) as u16;
|
||||
|
@ -632,6 +632,41 @@ async fn test_join_selections_space() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_join_selections_comment() -> anyhow::Result<()> {
|
||||
test((
|
||||
indoc! {"\
|
||||
/// #[a|]#bc
|
||||
/// def
|
||||
"},
|
||||
":lang rust<ret>J",
|
||||
indoc! {"\
|
||||
/// #[a|]#bc def
|
||||
"},
|
||||
))
|
||||
.await?;
|
||||
|
||||
// Only join if the comment token matches the previous line.
|
||||
test((
|
||||
indoc! {"\
|
||||
#[| // a
|
||||
// b
|
||||
/// c
|
||||
/// d
|
||||
e
|
||||
/// f
|
||||
// g]#
|
||||
"},
|
||||
":lang rust<ret>J",
|
||||
indoc! {"\
|
||||
#[| // a b /// c d e f // g]#
|
||||
"},
|
||||
))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_read_file() -> anyhow::Result<()> {
|
||||
let mut file = tempfile::NamedTempFile::new()?;
|
||||
|
@ -20,9 +20,9 @@ helix-core = { path = "../helix-core" }
|
||||
|
||||
bitflags = "2.6"
|
||||
cassowary = "0.3"
|
||||
unicode-segmentation = "1.11"
|
||||
unicode-segmentation = "1.12"
|
||||
crossterm = { version = "0.28", optional = true }
|
||||
termini = "1.0"
|
||||
serde = { version = "1", "optional" = true, features = ["derive"]}
|
||||
once_cell = "1.19"
|
||||
once_cell = "1.20"
|
||||
log = "~0.4"
|
||||
|
@ -29,4 +29,4 @@ log = "0.4"
|
||||
git = ["gix"]
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.12"
|
||||
tempfile = "3.13"
|
||||
|
@ -22,18 +22,24 @@
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
#[inline]
|
||||
fn get_repo_dir(file: &Path) -> Result<&Path> {
|
||||
file.parent().context("file has no parent directory")
|
||||
}
|
||||
|
||||
pub fn get_diff_base(file: &Path) -> Result<Vec<u8>> {
|
||||
debug_assert!(!file.exists() || file.is_file());
|
||||
debug_assert!(file.is_absolute());
|
||||
let file = gix::path::realpath(file).context("resolve symlinks")?;
|
||||
|
||||
// TODO cache repository lookup
|
||||
|
||||
let repo_dir = file.parent().context("file has no parent directory")?;
|
||||
let repo_dir = get_repo_dir(&file)?;
|
||||
let repo = open_repo(repo_dir)
|
||||
.context("failed to open git repo")?
|
||||
.to_thread_local();
|
||||
let head = repo.head_commit()?;
|
||||
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 data = file_object.detach().data;
|
||||
@ -56,7 +62,9 @@ pub fn get_diff_base(file: &Path) -> Result<Vec<u8>> {
|
||||
pub fn get_current_head_name(file: &Path) -> Result<Arc<ArcSwap<Box<str>>>> {
|
||||
debug_assert!(!file.exists() || file.is_file());
|
||||
debug_assert!(file.is_absolute());
|
||||
let repo_dir = file.parent().context("file has no parent directory")?;
|
||||
let file = gix::path::realpath(file).context("resolve symlinks")?;
|
||||
|
||||
let repo_dir = get_repo_dir(&file)?;
|
||||
let repo = open_repo(repo_dir)
|
||||
.context("failed to open git repo")?
|
||||
.to_thread_local();
|
||||
|
@ -98,9 +98,13 @@ fn directory() {
|
||||
assert!(git::get_diff_base(&dir).is_err());
|
||||
}
|
||||
|
||||
/// Test that `get_file_head` does not return content for a symlink.
|
||||
/// This is important to correctly cover cases where a symlink is removed and replaced by a file.
|
||||
/// If the contents of the symlink object were returned a diff between a path and the actual file would be produced (bad ui).
|
||||
/// Test that `get_diff_base` resolves symlinks so that the same diff base is
|
||||
/// used as the target file.
|
||||
///
|
||||
/// This is important to correctly cover cases where a symlink is removed and
|
||||
/// replaced by a file. If the contents of the symlink object were returned
|
||||
/// a diff between a literal file path and the actual file content would be
|
||||
/// produced (bad ui).
|
||||
#[cfg(any(unix, windows))]
|
||||
#[test]
|
||||
fn symlink() {
|
||||
@ -108,14 +112,41 @@ fn symlink() {
|
||||
use std::os::unix::fs::symlink;
|
||||
#[cfg(not(unix))]
|
||||
use std::os::windows::fs::symlink_file as symlink;
|
||||
|
||||
let temp_git = empty_git_repo();
|
||||
let file = temp_git.path().join("file.txt");
|
||||
let contents = b"foo".as_slice();
|
||||
File::create(&file).unwrap().write_all(contents).unwrap();
|
||||
let contents = Vec::from(b"foo");
|
||||
File::create(&file).unwrap().write_all(&contents).unwrap();
|
||||
let file_link = temp_git.path().join("file_link.txt");
|
||||
symlink("file.txt", &file_link).unwrap();
|
||||
|
||||
symlink("file.txt", &file_link).unwrap();
|
||||
create_commit(temp_git.path(), true);
|
||||
assert!(git::get_diff_base(&file_link).is_err());
|
||||
assert_eq!(git::get_diff_base(&file).unwrap(), Vec::from(contents));
|
||||
|
||||
assert_eq!(git::get_diff_base(&file_link).unwrap(), contents);
|
||||
assert_eq!(git::get_diff_base(&file).unwrap(), contents);
|
||||
}
|
||||
|
||||
/// Test that `get_diff_base` returns content when the file is a symlink to
|
||||
/// another file that is in a git repo, but the symlink itself is not.
|
||||
#[cfg(any(unix, windows))]
|
||||
#[test]
|
||||
fn symlink_to_git_repo() {
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::symlink;
|
||||
#[cfg(not(unix))]
|
||||
use std::os::windows::fs::symlink_file as symlink;
|
||||
|
||||
let temp_dir = tempfile::tempdir().expect("create temp dir");
|
||||
let temp_git = empty_git_repo();
|
||||
|
||||
let file = temp_git.path().join("file.txt");
|
||||
let contents = Vec::from(b"foo");
|
||||
File::create(&file).unwrap().write_all(&contents).unwrap();
|
||||
create_commit(temp_git.path(), true);
|
||||
|
||||
let file_link = temp_dir.path().join("file_link.txt");
|
||||
symlink(&file, &file_link).unwrap();
|
||||
|
||||
assert_eq!(git::get_diff_base(&file_link).unwrap(), contents);
|
||||
assert_eq!(git::get_diff_base(&file).unwrap(), contents);
|
||||
}
|
||||
|
@ -28,10 +28,10 @@ bitflags = "2.6"
|
||||
anyhow = "1"
|
||||
crossterm = { version = "0.28", optional = true }
|
||||
|
||||
tempfile = "3.12"
|
||||
tempfile = "3.13"
|
||||
|
||||
# Conversion traits
|
||||
once_cell = "1.19"
|
||||
once_cell = "1.20"
|
||||
url = "2.5.2"
|
||||
|
||||
arc-swap = { version = "1.7.1" }
|
||||
|
@ -422,7 +422,9 @@ pub fn get_terminal_provider() -> Option<TerminalConfig> {
|
||||
pub struct LspConfig {
|
||||
/// Enables LSP
|
||||
pub enable: bool,
|
||||
/// Display LSP progress messages below statusline
|
||||
/// Display LSP messagess from $/progress below statusline
|
||||
pub display_progress_messages: bool,
|
||||
/// Display LSP messages from window/showMessage below statusline
|
||||
pub display_messages: bool,
|
||||
/// Enable automatic pop up of signature help (parameter hints)
|
||||
pub auto_signature_help: bool,
|
||||
@ -440,7 +442,8 @@ impl Default for LspConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enable: true,
|
||||
display_messages: false,
|
||||
display_progress_messages: false,
|
||||
display_messages: true,
|
||||
auto_signature_help: true,
|
||||
display_signature_help_docs: true,
|
||||
display_inlay_hints: false,
|
||||
@ -1272,6 +1275,13 @@ pub fn set_error<T: Into<Cow<'static, str>>>(&mut self, error: T) {
|
||||
self.status_msg = Some((error, Severity::Error));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_warning<T: Into<Cow<'static, str>>>(&mut self, warning: T) {
|
||||
let warning = warning.into();
|
||||
log::warn!("editor warning: {}", warning);
|
||||
self.status_msg = Some((warning, Severity::Warning));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_status(&self) -> Option<(&Cow<'static, str>, &Severity)> {
|
||||
self.status_msg.as_ref().map(|(status, sev)| (status, sev))
|
||||
|
@ -247,7 +247,7 @@ fn apply_document_resource_op(
|
||||
match op {
|
||||
ResourceOp::Create(op) => {
|
||||
let uri = Uri::try_from(&op.uri)?;
|
||||
let path = uri.as_path_buf().expect("URIs are valid paths");
|
||||
let path = uri.as_path().expect("URIs are valid paths");
|
||||
let ignore_if_exists = op.options.as_ref().map_or(false, |options| {
|
||||
!options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false)
|
||||
});
|
||||
@ -259,13 +259,15 @@ fn apply_document_resource_op(
|
||||
}
|
||||
}
|
||||
|
||||
fs::write(&path, [])?;
|
||||
self.language_servers.file_event_handler.file_changed(path);
|
||||
fs::write(path, [])?;
|
||||
self.language_servers
|
||||
.file_event_handler
|
||||
.file_changed(path.to_path_buf());
|
||||
}
|
||||
}
|
||||
ResourceOp::Delete(op) => {
|
||||
let uri = Uri::try_from(&op.uri)?;
|
||||
let path = uri.as_path_buf().expect("URIs are valid paths");
|
||||
let path = uri.as_path().expect("URIs are valid paths");
|
||||
if path.is_dir() {
|
||||
let recursive = op
|
||||
.options
|
||||
@ -274,11 +276,13 @@ fn apply_document_resource_op(
|
||||
.unwrap_or(false);
|
||||
|
||||
if recursive {
|
||||
fs::remove_dir_all(&path)?
|
||||
fs::remove_dir_all(path)?
|
||||
} else {
|
||||
fs::remove_dir(&path)?
|
||||
fs::remove_dir(path)?
|
||||
}
|
||||
self.language_servers.file_event_handler.file_changed(path);
|
||||
self.language_servers
|
||||
.file_event_handler
|
||||
.file_changed(path.to_path_buf());
|
||||
} else if path.is_file() {
|
||||
fs::remove_file(path)?;
|
||||
}
|
||||
|
@ -53,20 +53,34 @@ pub fn new(dirs: &[PathBuf]) -> Self {
|
||||
|
||||
/// Loads a theme searching directories in priority order.
|
||||
pub fn load(&self, name: &str) -> Result<Theme> {
|
||||
let (theme, warnings) = self.load_with_warnings(name)?;
|
||||
|
||||
for warning in warnings {
|
||||
warn!("Theme '{}': {}", name, warning);
|
||||
}
|
||||
|
||||
Ok(theme)
|
||||
}
|
||||
|
||||
/// Loads a theme searching directories in priority order, returning any warnings
|
||||
pub fn load_with_warnings(&self, name: &str) -> Result<(Theme, Vec<String>)> {
|
||||
if name == "default" {
|
||||
return Ok(self.default());
|
||||
return Ok((self.default(), Vec::new()));
|
||||
}
|
||||
if name == "base16_default" {
|
||||
return Ok(self.base16_default());
|
||||
return Ok((self.base16_default(), Vec::new()));
|
||||
}
|
||||
|
||||
let mut visited_paths = HashSet::new();
|
||||
let theme = self.load_theme(name, &mut visited_paths).map(Theme::from)?;
|
||||
let (theme, warnings) = self
|
||||
.load_theme(name, &mut visited_paths)
|
||||
.map(Theme::from_toml)?;
|
||||
|
||||
Ok(Theme {
|
||||
let theme = Theme {
|
||||
name: name.into(),
|
||||
..theme
|
||||
})
|
||||
};
|
||||
Ok((theme, warnings))
|
||||
}
|
||||
|
||||
/// Recursively load a theme, merging with any inherited parent themes.
|
||||
@ -87,10 +101,7 @@ fn load_theme(&self, name: &str, visited_paths: &mut HashSet<PathBuf>) -> Result
|
||||
|
||||
let theme_toml = if let Some(parent_theme_name) = inherits {
|
||||
let parent_theme_name = parent_theme_name.as_str().ok_or_else(|| {
|
||||
anyhow!(
|
||||
"Theme: expected 'inherits' to be a string: {}",
|
||||
parent_theme_name
|
||||
)
|
||||
anyhow!("Expected 'inherits' to be a string: {}", parent_theme_name)
|
||||
})?;
|
||||
|
||||
let parent_theme_toml = match parent_theme_name {
|
||||
@ -181,9 +192,9 @@ fn path(&self, name: &str, visited_paths: &mut HashSet<PathBuf>) -> Result<PathB
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
if cycle_found {
|
||||
anyhow!("Theme: cycle found in inheriting: {}", name)
|
||||
anyhow!("Cycle found in inheriting: {}", name)
|
||||
} else {
|
||||
anyhow!("Theme: file not found for: {}", name)
|
||||
anyhow!("File not found for: {}", name)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -220,19 +231,11 @@ pub struct Theme {
|
||||
|
||||
impl From<Value> for Theme {
|
||||
fn from(value: Value) -> Self {
|
||||
if let Value::Table(table) = value {
|
||||
let (styles, scopes, highlights) = build_theme_values(table);
|
||||
|
||||
Self {
|
||||
styles,
|
||||
scopes,
|
||||
highlights,
|
||||
..Default::default()
|
||||
}
|
||||
} else {
|
||||
warn!("Expected theme TOML value to be a table, found {:?}", value);
|
||||
Default::default()
|
||||
let (theme, warnings) = Theme::from_toml(value);
|
||||
for warning in warnings {
|
||||
warn!("{}", warning);
|
||||
}
|
||||
theme
|
||||
}
|
||||
}
|
||||
|
||||
@ -242,31 +245,29 @@ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let values = Map::<String, Value>::deserialize(deserializer)?;
|
||||
|
||||
let (styles, scopes, highlights) = build_theme_values(values);
|
||||
|
||||
Ok(Self {
|
||||
styles,
|
||||
scopes,
|
||||
highlights,
|
||||
..Default::default()
|
||||
})
|
||||
let (theme, warnings) = Theme::from_keys(values);
|
||||
for warning in warnings {
|
||||
warn!("{}", warning);
|
||||
}
|
||||
Ok(theme)
|
||||
}
|
||||
}
|
||||
|
||||
fn build_theme_values(
|
||||
mut values: Map<String, Value>,
|
||||
) -> (HashMap<String, Style>, Vec<String>, Vec<Style>) {
|
||||
) -> (HashMap<String, Style>, Vec<String>, Vec<Style>, Vec<String>) {
|
||||
let mut styles = HashMap::new();
|
||||
let mut scopes = Vec::new();
|
||||
let mut highlights = Vec::new();
|
||||
|
||||
let mut warnings = Vec::new();
|
||||
|
||||
// TODO: alert user of parsing failures in editor
|
||||
let palette = values
|
||||
.remove("palette")
|
||||
.map(|value| {
|
||||
ThemePalette::try_from(value).unwrap_or_else(|err| {
|
||||
warn!("{}", err);
|
||||
warnings.push(err);
|
||||
ThemePalette::default()
|
||||
})
|
||||
})
|
||||
@ -279,7 +280,7 @@ fn build_theme_values(
|
||||
for (name, style_value) in values {
|
||||
let mut style = Style::default();
|
||||
if let Err(err) = palette.parse_style(&mut style, style_value) {
|
||||
warn!("{}", err);
|
||||
warnings.push(err);
|
||||
}
|
||||
|
||||
// these are used both as UI and as highlights
|
||||
@ -288,7 +289,7 @@ fn build_theme_values(
|
||||
highlights.push(style);
|
||||
}
|
||||
|
||||
(styles, scopes, highlights)
|
||||
(styles, scopes, highlights, warnings)
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
@ -354,6 +355,27 @@ pub fn is_16_color(&self) -> bool {
|
||||
.all(|color| !matches!(color, Some(Color::Rgb(..))))
|
||||
})
|
||||
}
|
||||
|
||||
fn from_toml(value: Value) -> (Self, Vec<String>) {
|
||||
if let Value::Table(table) = value {
|
||||
Theme::from_keys(table)
|
||||
} else {
|
||||
warn!("Expected theme TOML value to be a table, found {:?}", value);
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn from_keys(toml_keys: Map<String, Value>) -> (Self, Vec<String>) {
|
||||
let (styles, scopes, highlights, load_errors) = build_theme_values(toml_keys);
|
||||
|
||||
let theme = Self {
|
||||
styles,
|
||||
scopes,
|
||||
highlights,
|
||||
..Default::default()
|
||||
};
|
||||
(theme, load_errors)
|
||||
}
|
||||
}
|
||||
|
||||
struct ThemePalette {
|
||||
@ -408,7 +430,7 @@ fn ansi_string_to_rgb(s: &str) -> Result<Color, String> {
|
||||
if let Ok(index) = s.parse::<u8>() {
|
||||
return Ok(Color::Indexed(index));
|
||||
}
|
||||
Err(format!("Theme: malformed ANSI: {}", s))
|
||||
Err(format!("Malformed ANSI: {}", s))
|
||||
}
|
||||
|
||||
fn hex_string_to_rgb(s: &str) -> Result<Color, String> {
|
||||
@ -422,13 +444,13 @@ fn hex_string_to_rgb(s: &str) -> Result<Color, String> {
|
||||
}
|
||||
}
|
||||
|
||||
Err(format!("Theme: malformed hexcode: {}", s))
|
||||
Err(format!("Malformed hexcode: {}", s))
|
||||
}
|
||||
|
||||
fn parse_value_as_str(value: &Value) -> Result<&str, String> {
|
||||
value
|
||||
.as_str()
|
||||
.ok_or(format!("Theme: unrecognized value: {}", value))
|
||||
.ok_or(format!("Unrecognized value: {}", value))
|
||||
}
|
||||
|
||||
pub fn parse_color(&self, value: Value) -> Result<Color, String> {
|
||||
@ -445,14 +467,14 @@ pub fn parse_modifier(value: &Value) -> Result<Modifier, String> {
|
||||
value
|
||||
.as_str()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.ok_or(format!("Theme: invalid modifier: {}", value))
|
||||
.ok_or(format!("Invalid modifier: {}", value))
|
||||
}
|
||||
|
||||
pub fn parse_underline_style(value: &Value) -> Result<UnderlineStyle, String> {
|
||||
value
|
||||
.as_str()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.ok_or(format!("Theme: invalid underline style: {}", value))
|
||||
.ok_or(format!("Invalid underline style: {}", value))
|
||||
}
|
||||
|
||||
pub fn parse_style(&self, style: &mut Style, value: Value) -> Result<(), String> {
|
||||
@ -462,9 +484,7 @@ pub fn parse_style(&self, style: &mut Style, value: Value) -> Result<(), String>
|
||||
"fg" => *style = style.fg(self.parse_color(value)?),
|
||||
"bg" => *style = style.bg(self.parse_color(value)?),
|
||||
"underline" => {
|
||||
let table = value
|
||||
.as_table_mut()
|
||||
.ok_or("Theme: underline must be table")?;
|
||||
let table = value.as_table_mut().ok_or("Underline must be table")?;
|
||||
if let Some(value) = table.remove("color") {
|
||||
*style = style.underline_color(self.parse_color(value)?);
|
||||
}
|
||||
@ -473,13 +493,11 @@ pub fn parse_style(&self, style: &mut Style, value: Value) -> Result<(), String>
|
||||
}
|
||||
|
||||
if let Some(attr) = table.keys().next() {
|
||||
return Err(format!("Theme: invalid underline attribute: {attr}"));
|
||||
return Err(format!("Invalid underline attribute: {attr}"));
|
||||
}
|
||||
}
|
||||
"modifiers" => {
|
||||
let modifiers = value
|
||||
.as_array()
|
||||
.ok_or("Theme: modifiers should be an array")?;
|
||||
let modifiers = value.as_array().ok_or("Modifiers should be an array")?;
|
||||
|
||||
for modifier in modifiers {
|
||||
if modifier
|
||||
@ -492,7 +510,7 @@ pub fn parse_style(&self, style: &mut Style, value: Value) -> Result<(), String>
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => return Err(format!("Theme: invalid style attribute: {}", name)),
|
||||
_ => return Err(format!("Invalid style attribute: {}", name)),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -16,6 +16,7 @@ bicep-langserver = { command = "bicep-langserver" }
|
||||
bitbake-language-server = { command = "bitbake-language-server" }
|
||||
bufls = { command = "bufls", args = ["serve"] }
|
||||
cairo-language-server = { command = "cairo-language-server", args = [] }
|
||||
circom-lsp = { command = "circom-lsp" }
|
||||
cl-lsp = { command = "cl-lsp", args = [ "stdio" ] }
|
||||
clangd = { command = "clangd" }
|
||||
clojure-lsp = { command = "clojure-lsp" }
|
||||
@ -32,6 +33,7 @@ dot-language-server = { command = "dot-language-server", args = ["--stdio"] }
|
||||
earthlyls = { command = "earthlyls" }
|
||||
elixir-ls = { command = "elixir-ls", config = { elixirLS.dialyzerEnabled = false } }
|
||||
elm-language-server = { command = "elm-language-server" }
|
||||
elp = { command = "elp", args = ["server"] }
|
||||
elvish = { command = "elvish", args = ["-lsp"] }
|
||||
erlang-ls = { command = "erlang_ls" }
|
||||
forc = { command = "forc", args = ["lsp"] }
|
||||
@ -44,12 +46,13 @@ haskell-language-server = { command = "haskell-language-server-wrapper", args =
|
||||
idris2-lsp = { command = "idris2-lsp" }
|
||||
intelephense = { command = "intelephense", args = ["--stdio"] }
|
||||
jdtls = { command = "jdtls" }
|
||||
jedi = { command = "jedi-language-server" }
|
||||
jq-lsp = { command = "jq-lsp" }
|
||||
jsonnet-language-server = { command = "jsonnet-language-server", args= ["-t", "--lint"] }
|
||||
julia = { command = "julia", timeout = 60, args = [ "--startup-file=no", "--history-file=no", "--quiet", "-e", "using LanguageServer; runserver()", ] }
|
||||
koka = { command = "koka", args = ["--language-server", "--lsstdio"] }
|
||||
kotlin-language-server = { command = "kotlin-language-server" }
|
||||
lean = { command = "lean", args = [ "--server" ] }
|
||||
lean = { command = "lean", args = [ "--server", "--memory=1024" ] }
|
||||
ltex-ls = { command = "ltex-ls" }
|
||||
markdoc-ls = { command = "markdoc-ls", args = ["--stdio"] }
|
||||
markdown-oxide = { command = "markdown-oxide" }
|
||||
@ -84,6 +87,7 @@ racket = { command = "racket", args = ["-l", "racket-langserver"] }
|
||||
regols = { command = "regols" }
|
||||
rescript-language-server = { command = "rescript-language-server", args = ["--stdio"] }
|
||||
robotframework_ls = { command = "robotframework_ls" }
|
||||
ruff = { command = "ruff", args = ["server"] }
|
||||
serve-d = { command = "serve-d" }
|
||||
slint-lsp = { command = "slint-lsp", args = [] }
|
||||
solargraph = { command = "solargraph", args = ["stdio"] }
|
||||
@ -91,6 +95,7 @@ solc = { command = "solc", args = ["--lsp"] }
|
||||
sourcekit-lsp = { command = "sourcekit-lsp" }
|
||||
svlangserver = { command = "svlangserver", args = [] }
|
||||
swipl = { command = "swipl", args = [ "-g", "use_module(library(lsp_server))", "-g", "lsp_server:main", "-t", "halt", "--", "stdio" ] }
|
||||
superhtml = { command = "superhtml", args = ["lsp"]}
|
||||
tailwindcss-ls = { command = "tailwindcss-language-server", args = ["--stdio"] }
|
||||
taplo = { command = "taplo", args = ["lsp", "stdio"] }
|
||||
templ = { command = "templ", args = ["lsp"] }
|
||||
@ -98,6 +103,7 @@ terraform-ls = { command = "terraform-ls", args = ["serve"] }
|
||||
texlab = { command = "texlab" }
|
||||
typespec = { command = "tsp-server", args = ["--stdio"] }
|
||||
vala-language-server = { command = "vala-language-server" }
|
||||
vale-ls = { command = "vale-ls" }
|
||||
vhdl_ls = { command = "vhdl_ls", args = [] }
|
||||
vlang-language-server = { command = "v-analyzer" }
|
||||
vscode-css-language-server = { command = "vscode-css-language-server", args = ["--stdio"], config = { provideFormatter = true, css = { validate = { enable = true } } } }
|
||||
@ -443,6 +449,8 @@ file-types = [
|
||||
{ glob = ".watchmanconfig" },
|
||||
"avsc",
|
||||
{ glob = ".prettierrc" },
|
||||
"ldtk",
|
||||
"ldtkl",
|
||||
]
|
||||
language-servers = [ "vscode-json-language-server" ]
|
||||
auto-format = true
|
||||
@ -836,7 +844,7 @@ scope = "text.html.basic"
|
||||
injection-regex = "html"
|
||||
file-types = ["html", "htm", "shtml", "xhtml", "xht", "jsp", "asp", "aspx", "jshtm", "volt", "rhtml", "cshtml"]
|
||||
block-comment-tokens = { start = "<!--", end = "-->" }
|
||||
language-servers = [ "vscode-html-language-server" ]
|
||||
language-servers = [ "vscode-html-language-server", "superhtml" ]
|
||||
auto-format = true
|
||||
indent = { tab-width = 2, unit = " " }
|
||||
|
||||
@ -852,7 +860,7 @@ file-types = ["py", "pyi", "py3", "pyw", "ptl", "rpy", "cpy", "ipy", "pyt", { gl
|
||||
shebangs = ["python"]
|
||||
roots = ["pyproject.toml", "setup.py", "poetry.lock", "pyrightconfig.json"]
|
||||
comment-token = "#"
|
||||
language-servers = [ "pylsp" ]
|
||||
language-servers = ["ruff", "jedi", "pylsp"]
|
||||
# TODO: pyls needs utf-8 offsets
|
||||
indent = { tab-width = 4, unit = " " }
|
||||
|
||||
@ -878,7 +886,7 @@ indent = { tab-width = 2, unit = " " }
|
||||
|
||||
[[grammar]]
|
||||
name = "nickel"
|
||||
source = { git = "https://github.com/nickel-lang/tree-sitter-nickel", rev = "e1d9337864d209898a08c26b8cd4c2dd14c15148" }
|
||||
source = { git = "https://github.com/nickel-lang/tree-sitter-nickel", rev = "88d836a24b3b11c8720874a1a9286b8ae838d30a" }
|
||||
|
||||
[[language]]
|
||||
name = "nix"
|
||||
@ -909,6 +917,7 @@ file-types = [
|
||||
"podspec",
|
||||
"rjs",
|
||||
"rbi",
|
||||
"rbs",
|
||||
{ glob = "rakefile" },
|
||||
{ glob = "gemfile" },
|
||||
{ glob = "Rakefile" },
|
||||
@ -1792,7 +1801,7 @@ roots = ["rebar.config"]
|
||||
shebangs = ["escript"]
|
||||
comment-token = "%%"
|
||||
indent = { tab-width = 4, unit = " " }
|
||||
language-servers = [ "erlang-ls" ]
|
||||
language-servers = [ "erlang-ls", "elp" ]
|
||||
|
||||
[[grammar]]
|
||||
name = "erlang"
|
||||
@ -1826,7 +1835,7 @@ auto-format = true
|
||||
|
||||
[[grammar]]
|
||||
name = "hcl"
|
||||
source = { git = "https://github.com/MichaHoffmann/tree-sitter-hcl", rev = "3cb7fc28247efbcb2973b97e71c78838ad98a583" }
|
||||
source = { git = "https://github.com/tree-sitter-grammars/tree-sitter-hcl", rev = "9e3ec9848f28d26845ba300fd73c740459b83e9b" }
|
||||
|
||||
[[language]]
|
||||
name = "tfvars"
|
||||
@ -2130,7 +2139,7 @@ language-servers = [ "ols" ]
|
||||
comment-token = "//"
|
||||
block-comment-tokens = { start = "/*", end = "*/" }
|
||||
indent = { tab-width = 4, unit = "\t" }
|
||||
formatter = { command = "odinfmt", args = [ "-stdin", "true" ] }
|
||||
formatter = { command = "odinfmt", args = [ "-stdin" ] }
|
||||
|
||||
[language.debugger]
|
||||
name = "lldb-dap"
|
||||
@ -2397,7 +2406,7 @@ language-servers = [ "slint-lsp" ]
|
||||
|
||||
[[grammar]]
|
||||
name = "slint"
|
||||
source = { git = "https://github.com/slint-ui/tree-sitter-slint", rev = "4a0558cc0fcd7a6110815b9bbd7cc12d7ab31e74" }
|
||||
source = { git = "https://github.com/slint-ui/tree-sitter-slint", rev = "34ccfd58d3baee7636f62d9326f32092264e8407" }
|
||||
|
||||
[[language]]
|
||||
name = "task"
|
||||
@ -3786,3 +3795,18 @@ indent = { tab-width = 2, unit = " " }
|
||||
[[grammar]]
|
||||
name = "thrift"
|
||||
source = { git = "https://github.com/tree-sitter-grammars/tree-sitter-thrift" , rev = "68fd0d80943a828d9e6f49c58a74be1e9ca142cf" }
|
||||
|
||||
[[language]]
|
||||
name = "circom"
|
||||
scope = "source.circom"
|
||||
injection-regex = "circom"
|
||||
file-types = ["circom"]
|
||||
roots = ["package.json"]
|
||||
comment-tokens = "//"
|
||||
indent = { tab-width = 4, unit = " " }
|
||||
auto-format = false
|
||||
language-servers = ["circom-lsp"]
|
||||
|
||||
[[grammar]]
|
||||
name = "circom"
|
||||
source = { git = "https://github.com/Decurity/tree-sitter-circom", rev = "02150524228b1e6afef96949f2d6b7cc0aaf999e" }
|
||||
|
142
runtime/queries/circom/highlights.scm
Normal file
142
runtime/queries/circom/highlights.scm
Normal file
@ -0,0 +1,142 @@
|
||||
; identifiers
|
||||
; -----------
|
||||
(identifier) @variable
|
||||
|
||||
; Pragma
|
||||
; -----------
|
||||
(pragma_directive) @keyword.directive
|
||||
|
||||
; Include
|
||||
; -----------
|
||||
(include_directive) @keyword.directive
|
||||
|
||||
; Literals
|
||||
; --------
|
||||
(string) @string
|
||||
(int_literal) @constant.numeric.integer
|
||||
(comment) @comment
|
||||
|
||||
; Definitions
|
||||
; -----------
|
||||
(function_definition
|
||||
name: (identifier) @keyword.function)
|
||||
|
||||
(template_definition
|
||||
name: (identifier) @keyword.function)
|
||||
|
||||
; Use contructor coloring for special functions
|
||||
(main_component_definition) @constructor
|
||||
|
||||
; Invocations
|
||||
(call_expression . (identifier) @function)
|
||||
|
||||
; Function parameters
|
||||
(parameter name: (identifier) @variable.parameter)
|
||||
|
||||
; Members
|
||||
(member_expression property: (property_identifier) @variable.other.member)
|
||||
|
||||
; Tokens
|
||||
; -------
|
||||
|
||||
; Keywords
|
||||
[
|
||||
"signal"
|
||||
"var"
|
||||
"component"
|
||||
] @keyword.storage.type
|
||||
|
||||
[ "include" ] @keyword.control.import
|
||||
|
||||
[
|
||||
"public"
|
||||
"input"
|
||||
"output"
|
||||
] @keyword.storage.modifier
|
||||
|
||||
[
|
||||
"for"
|
||||
"while"
|
||||
] @keyword.control.repeat
|
||||
|
||||
[
|
||||
"if"
|
||||
"else"
|
||||
] @keyword.control.conditional
|
||||
|
||||
[
|
||||
"return"
|
||||
] @keyword.control.return
|
||||
|
||||
[
|
||||
"function"
|
||||
"template"
|
||||
] @keyword.function
|
||||
|
||||
; Punctuation
|
||||
[
|
||||
"("
|
||||
")"
|
||||
"["
|
||||
"]"
|
||||
"{"
|
||||
"}"
|
||||
] @punctuation.bracket
|
||||
|
||||
[
|
||||
"."
|
||||
","
|
||||
";"
|
||||
] @punctuation.delimiter
|
||||
|
||||
; Operators
|
||||
; https://docs.circom.io/circom-language/basic-operators
|
||||
[
|
||||
"="
|
||||
"?"
|
||||
"&&"
|
||||
"||"
|
||||
"!"
|
||||
"<"
|
||||
">"
|
||||
"<="
|
||||
">="
|
||||
"=="
|
||||
"!="
|
||||
"+"
|
||||
"-"
|
||||
"*"
|
||||
"**"
|
||||
"/"
|
||||
"\\"
|
||||
"%"
|
||||
"+="
|
||||
"-="
|
||||
"*="
|
||||
"**="
|
||||
"/="
|
||||
"\\="
|
||||
"%="
|
||||
"++"
|
||||
"--"
|
||||
"&"
|
||||
"|"
|
||||
"~"
|
||||
"^"
|
||||
">>"
|
||||
"<<"
|
||||
"&="
|
||||
"|="
|
||||
; "\~=" ; bug, uncomment and circom will not highlight
|
||||
"^="
|
||||
">>="
|
||||
"<<="
|
||||
] @operator
|
||||
|
||||
[
|
||||
"<=="
|
||||
"==>"
|
||||
"<--"
|
||||
"-->"
|
||||
"==="
|
||||
] @operator
|
9
runtime/queries/circom/locals.scm
Normal file
9
runtime/queries/circom/locals.scm
Normal file
@ -0,0 +1,9 @@
|
||||
(function_definition) @local.scope
|
||||
(template_definition) @local.scope
|
||||
(main_component_definition) @local.scope
|
||||
(block_statement) @local.scope
|
||||
|
||||
(parameter name: (identifier) @local.definition) @local.definition
|
||||
|
||||
|
||||
(identifier) @local.reference
|
@ -101,19 +101,19 @@
|
||||
]
|
||||
)
|
||||
|
||||
; non-builtin command names
|
||||
(command name: (word) @function)
|
||||
|
||||
; derived from builtin -n (fish 3.2.2)
|
||||
; derived from builtin -n (fish 3.7.1)
|
||||
(command
|
||||
name: [
|
||||
(word) @function.builtin
|
||||
(#match? @function.builtin "^(\.|:|_|alias|argparse|bg|bind|block|breakpoint|builtin|cd|command|commandline|complete|contains|count|disown|echo|emit|eval|exec|exit|fg|functions|history|isatty|jobs|math|printf|pwd|random|read|realpath|set|set_color|source|status|string|test|time|type|ulimit|wait)$")
|
||||
(#any-of? @function.builtin "abbr" "alias" "and" "argparse" "begin" "bg" "bind" "block" "break" "breakpoint" "builtin" "case" "cd" "command" "commandline" "complete" "contains" "continue" "count" "disown" "echo" "else" "emit" "end" "eval" "exec" "exit" "false" "fg" "for" "function" "functions" "history" "if" "isatty" "jobs" "math" "not" "or" "path" "printf" "pwd" "random" "read" "realpath" "return" "set" "set_color" "source" "status" "string" "switch" "test" "time" "true" "type" "ulimit" "wait" "while")
|
||||
]
|
||||
)
|
||||
|
||||
(test_command "test" @function.builtin)
|
||||
|
||||
; non-builtin command names
|
||||
(command name: (word) @function)
|
||||
|
||||
;; Functions
|
||||
|
||||
(function_definition ["function" "end"] @keyword.function)
|
||||
|
@ -23,17 +23,19 @@
|
||||
(let_in_block
|
||||
"let" @keyword
|
||||
"rec"? @keyword
|
||||
"in" @keyword
|
||||
)
|
||||
|
||||
(let_binding
|
||||
pat: (pattern
|
||||
(ident) @variable
|
||||
)
|
||||
"in" @keyword
|
||||
)
|
||||
|
||||
(fun_expr
|
||||
"fun" @keyword.function
|
||||
pats:
|
||||
(pattern
|
||||
id: (ident) @variable.parameter
|
||||
)+
|
||||
(pattern_fun (ident) @variable.parameter)+
|
||||
"=>" @operator
|
||||
)
|
||||
(record_field) @variable.other.member
|
||||
|
@ -4,10 +4,13 @@
|
||||
] @keyword.directive
|
||||
|
||||
[
|
||||
"import"
|
||||
"package"
|
||||
] @namespace
|
||||
|
||||
[
|
||||
"import"
|
||||
] @keyword.control.import
|
||||
|
||||
[
|
||||
"foreign"
|
||||
"using"
|
||||
@ -200,7 +203,7 @@
|
||||
|
||||
(struct . (identifier) @type)
|
||||
|
||||
(field_type . (identifier) "." (identifier) @type)
|
||||
(field_type . (identifier) @keyword.storage.type "." (identifier) @type)
|
||||
|
||||
(bit_set_type (identifier) @type ";")
|
||||
|
||||
@ -248,6 +251,8 @@ (struct . (identifier) @type)
|
||||
|
||||
(using_statement (identifier) @namespace)
|
||||
|
||||
(import_declaration (identifier) @keyword.storage.type)
|
||||
|
||||
; Parameters
|
||||
|
||||
(parameter (identifier) @variable.parameter ":" "="? (identifier)? @constant)
|
||||
|
@ -6,9 +6,13 @@
|
||||
|
||||
(attribute
|
||||
(attribute_name) @attribute
|
||||
(quoted_attribute_value
|
||||
(attribute_value) @string)
|
||||
)
|
||||
[(attribute_value) (quoted_attribute_value)]? @string)
|
||||
|
||||
(directive_attribute
|
||||
(directive_name) @attribute
|
||||
(directive_argument)? @attribute
|
||||
(directive_modifiers)? @attribute
|
||||
[(attribute_value) (quoted_attribute_value)]? @string)
|
||||
|
||||
(comment) @comment
|
||||
|
||||
@ -18,4 +22,7 @@
|
||||
"</"
|
||||
"{{"
|
||||
"}}"
|
||||
"/>"
|
||||
] @punctuation.bracket
|
||||
"=" @punctuation.delimiter
|
||||
|
||||
|
@ -68,8 +68,10 @@
|
||||
"ui.statusline.select" = { fg = "my_gray7", bg = "my_black", modifiers = ["bold"] }
|
||||
"ui.text.focus" = "my_white1"
|
||||
"ui.text" = "my_white1"
|
||||
"ui.virtual.inlay-hint" = { fg = "my_gray4", bg="my_black", modifiers = ["normal"] }
|
||||
"ui.virtual.inlay-hint.parameter" = { fg = "my_gray4", modifiers = ["normal"] }
|
||||
# Invalid modifier: "normal". See 'https://github.com/helix-editor/helix/issues/5709'
|
||||
"ui.virtual.inlay-hint" = { fg = "my_gray4", bg="my_black" } #, modifiers = ["normal"] }
|
||||
# "ui.virtual.inlay-hint.parameter" = { fg = "my_gray4", modifiers = ["normal"] }
|
||||
"ui.virtual.inlay-hint.parameter" = "my_gray4"
|
||||
"ui.virtual.inlay-hint.type" = { fg = "my_gray4", modifiers = ["italic"] }
|
||||
"ui.virtual.jump-label" = { fg = "my_yellow2", modifiers = ["bold"] }
|
||||
"ui.virtual.ruler" = { bg = "my_gray1" }
|
||||
|
@ -59,8 +59,10 @@
|
||||
"ui.text.focus" = { fg = "bogster-fg1", modifiers= ["bold"] }
|
||||
"ui.virtual.whitespace" = "bogster-base5"
|
||||
"ui.virtual.ruler" = { bg = "bogster-base0" }
|
||||
"ui.virtual.jump-label" = { fg = "bogster-base0", bg = "bogster-yellow", modifiers = [ "bold" ] }
|
||||
|
||||
"ui.selection" = { bg = "bogster-base3" }
|
||||
"ui.selection" = { bg = "bogster-base2" }
|
||||
"ui.selection.primary" = { bg = "bogster-base3" }
|
||||
"ui.cursor.match" = { fg = "bogster-base3", bg = "bogster-orange" }
|
||||
"ui.cursor" = { fg = "bogster-base5", modifiers = ["reversed"] }
|
||||
|
||||
|
@ -28,6 +28,8 @@
|
||||
"ui.virtual.jump-label" = { fg = "lightblue", modifiers = ["italic", "bold"] }
|
||||
"ui.bufferline" = { fg = "grey04", bg = "grey00" }
|
||||
"ui.bufferline.active" = { fg = "grey07", bg = "grey02" }
|
||||
"ui.picker.header.column" = { fg = "grey05", modifiers = ["italic", "bold"] }
|
||||
"ui.picker.header.column.active" = { fg = "grey05", bg = "grey03", modifiers = ["italic", "bold"] }
|
||||
|
||||
"operator" = "grey05"
|
||||
"variable" = "white"
|
||||
|
@ -68,7 +68,8 @@
|
||||
"ui.menu.selected" = { fg = "dark_red", bg = "light_blue" }
|
||||
"ui.selection" = { bg = "lightgoldenrod1" }
|
||||
"ui.selection.primary" = { bg = "lightgoldenrod2" }
|
||||
"ui.virtual.whitespace" = "highlight"
|
||||
# Malformed ANSI: highlight. See 'https://github.com/helix-editor/helix/issues/5709'
|
||||
# "ui.virtual.whitespace" = "highlight"
|
||||
"ui.virtual.ruler" = { bg = "gray95" }
|
||||
"ui.virtual.inlay-hint" = { fg = "gray75" }
|
||||
"ui.cursorline.primary" = { bg = "darkseagreen2" }
|
||||
|
@ -61,8 +61,11 @@
|
||||
"ui.virtual" = { fg = "base5", bg = "base6" }
|
||||
"ui.virtual.whitespace" = { fg = "base5" }
|
||||
"ui.virtual.ruler" = { bg = "base6" }
|
||||
"ui.virtual.inlay-hint" = { fg = "base4", modifiers = ["normal"] }
|
||||
"ui.virtual.inlay-hint.parameter" = { fg = "base3", modifiers = ["normal"] }
|
||||
# Invalid modifier: "normal". See 'https://github.com/helix-editor/helix/issues/5709'
|
||||
# "ui.virtual.inlay-hint" = { fg = "base4", modifiers = ["normal"] }
|
||||
# "ui.virtual.inlay-hint.parameter" = { fg = "base3", modifiers = ["normal"] }
|
||||
"ui.virtual.inlay-hint" = "base4"
|
||||
"ui.virtual.inlay-hint.parameter" = "base3"
|
||||
"ui.virtual.inlay-hint.type" = { fg = "base3", modifiers = ["italic"] }
|
||||
|
||||
"ui.linenr" = { bg = "base6" }
|
||||
|
@ -67,6 +67,7 @@
|
||||
"ui.virtual.ruler" = { bg = "bg1" }
|
||||
"ui.virtual.inlay-hint" = { fg = "bg7" }
|
||||
"ui.virtual.wrap" = { fg = "bg2" }
|
||||
"ui.virtual.jump-label" = { fg = "red3", modifiers = ["bold"] }
|
||||
|
||||
"diagnostic.warning" = { underline = { color = "orange1", style = "dashed" } }
|
||||
"diagnostic.error" = { underline = { color = "red3", style = "dashed" } }
|
||||
|
@ -43,6 +43,8 @@ tag = "red"
|
||||
"ui.bufferline" = { bg = "dark-bg", fg = "light-gray" }
|
||||
"ui.bufferline.active" = { bg = "dark-bg", fg = "orange" }
|
||||
"ui.virtual.jump-label" = { fg = "pink", modifiers = ["bold"] }
|
||||
"ui.picker.header.column" = { fg = "orange", underline.style = "line" }
|
||||
"ui.picker.header.column.active" = { fg = "purple", modifiers = ["bold"], underline.style = "line" }
|
||||
|
||||
# Diagnostics
|
||||
"diagnostic" = { underline = { style = "curl" } }
|
||||
|
@ -25,7 +25,8 @@
|
||||
"ui.statusline.normal" = { fg = "sumiInk0", bg = "crystalBlue", modifiers = ["bold"] }
|
||||
"ui.statusline.insert" = { fg = "sumiInk0", bg = "autumnGreen", modifiers = ["bold"] }
|
||||
"ui.statusline.select" = { fg = "sumiInk0", bg = "oniViolet", modifiers = ["bold"] }
|
||||
"ui.statusline.separator" = { fg = "", bg = "" }
|
||||
# Malformed ANSI: "". See 'https://github.com/helix-editor/helix/issues/5709'
|
||||
# "ui.statusline.separator" = { fg = "", bg = "" }
|
||||
|
||||
"ui.bufferline" = { fg = "fujiGray", bg = "sumiInk0" }
|
||||
"ui.bufferline.active" = { fg = "oldWhite", bg = "sumiInk0" }
|
||||
|
@ -79,7 +79,8 @@
|
||||
"ui.statusline" = { fg = "active_text", bg = "#414339" }
|
||||
"ui.statusline.inactive" = { fg = "active_text", bg = "#75715e" }
|
||||
|
||||
"ui.bufferline" = { fg = "grey2", bg = "bg3" }
|
||||
# Malformed ANSI: grey2, bg3. See 'https://github.com/helix-editor/helix/issues/5709'
|
||||
# "ui.bufferline" = { fg = "grey2", bg = "bg3" }
|
||||
"ui.bufferline.active" = { fg = "active_text", bg = "selection", modifiers = [
|
||||
"bold",
|
||||
] }
|
||||
|
@ -8,9 +8,12 @@ inherits = "monokai"
|
||||
|
||||
"type" = { fg = "type", modifiers = ["bold"] }
|
||||
|
||||
"ui.statusline.normal" = { fg = "light-black", bg = "cyan" }
|
||||
"ui.statusline.insert" = { fg = "light-black", bg = "green" }
|
||||
"ui.statusline.select" = { fg = "light-black", bg = "purple" }
|
||||
# Malformed ANSI: light-black, purple. See 'https://github.com/helix-editor/helix/issues/5709'
|
||||
# "ui.statusline.normal" = { fg = "light-black", bg = "cyan" }
|
||||
"ui.statusline.normal" = { bg = "cyan" }
|
||||
# "ui.statusline.insert" = { fg = "light-black", bg = "green" }
|
||||
"ui.statusline.insert" = { bg = "green" }
|
||||
# "ui.statusline.select" = { fg = "light-black", bg = "purple" }
|
||||
|
||||
"ui.virtual.jump-label" = { fg = "cyan", modifiers = ["bold"] }
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
"keyword.control" = { fg = "purple" }
|
||||
"keyword.control.import" = { fg = "red" }
|
||||
"keyword.directive" = { fg = "purple" }
|
||||
"keyword.storage" = { fg = "purple" }
|
||||
"label" = { fg = "purple" }
|
||||
"namespace" = { fg = "blue" }
|
||||
"operator" = { fg = "purple" }
|
||||
|
@ -1,60 +1,61 @@
|
||||
# Author : Timothy DeHerrera <tim@nrdxp.dev>
|
||||
"comment".fg = "comment"
|
||||
|
||||
"constant".fg = "purple"
|
||||
"constant.builtin".fg = "olive"
|
||||
"constant.character".fg = "carnation"
|
||||
"constant.character.escape".fg = "magenta"
|
||||
"constant.character".fg = "carnation"
|
||||
"constant".fg = "purple"
|
||||
"constant.numeric".fg = "cyan"
|
||||
"constant.numeric.float".fg = "red"
|
||||
|
||||
"function".fg = "green"
|
||||
"function.builtin".fg = "sand"
|
||||
"function".fg = "green"
|
||||
"function.macro".fg = "blue"
|
||||
"function.method".fg = "opal"
|
||||
|
||||
"keyword" = { fg = "magenta", modifiers = ["bold"] }
|
||||
"keyword.operator" = { fg = "coral", modifiers = ["bold"] }
|
||||
"keyword.function" = { fg = "lilac", modifiers = ["bold"] }
|
||||
"keyword.control" = { fg = "carnation", modifiers = ["bold"] }
|
||||
"keyword.control.exception" = { fg = "red", modifiers = ["bold"] }
|
||||
"keyword.function" = { fg = "lilac", modifiers = ["bold"] }
|
||||
"keyword.operator" = { fg = "coral", modifiers = ["bold"] }
|
||||
"keyword.storage" = { fg = "coral", modifiers = ["bold"] }
|
||||
|
||||
"operator".fg = "coral"
|
||||
|
||||
"punctuation".fg = "magenta"
|
||||
"punctuation.delimiter".fg = "coral"
|
||||
"punctuation.bracket".fg = "foreground"
|
||||
"punctuation.delimiter".fg = "coral"
|
||||
"punctuation".fg = "magenta"
|
||||
|
||||
"string".fg = "yellow"
|
||||
"string.special".fg = "blue"
|
||||
"string.regexp".fg = "red"
|
||||
"tag".fg = "carnation"
|
||||
"attribute".fg = "opal"
|
||||
"string".fg = "yellow"
|
||||
"string.regexp".fg = "red"
|
||||
"string.special".fg = "blue"
|
||||
"tag".fg = "carnation"
|
||||
|
||||
"type".fg = "opal"
|
||||
"type.variant".fg = "sand"
|
||||
"type.builtin".fg = "yellow"
|
||||
"type.enum.variant".fg = "sand"
|
||||
"type".fg = "opal"
|
||||
"type.variant".fg = "sand"
|
||||
|
||||
"variable".fg = "cyan"
|
||||
"variable.builtin".fg = "olive"
|
||||
"variable".fg = "cyan"
|
||||
"variable.other.member".fg = "lilac"
|
||||
"variable.parameter" = { fg = "blue", modifiers = ["italic"] }
|
||||
|
||||
"namespace".fg = "olive"
|
||||
"constructor".fg = "sand"
|
||||
"special".fg = "magenta"
|
||||
"label".fg = "magenta"
|
||||
"namespace".fg = "olive"
|
||||
"special".fg = "magenta"
|
||||
|
||||
"diff.plus".fg = "green"
|
||||
"diff.delta".fg = "blue"
|
||||
"diff.minus".fg = "red"
|
||||
"diff.plus".fg = "green"
|
||||
|
||||
"ui.background" = { fg = "foreground", bg = "background" }
|
||||
"ui.cursor" = { fg = "background", bg = "blue", modifiers = ["dim"] }
|
||||
"ui.cursor.match" = { fg = "green", modifiers = ["underlined"] }
|
||||
"ui.cursor.primary" = { fg = "background", bg = "cyan", modifiers = ["dim"] }
|
||||
"ui.cursorline" = { bg = "background_dark" }
|
||||
"ui.help" = { fg = "foreground", bg = "background_dark" }
|
||||
"ui.linenr" = { fg = "comment" }
|
||||
"ui.linenr.selected" = { fg = "foreground" }
|
||||
@ -63,7 +64,6 @@
|
||||
"ui.popup" = { fg = "foreground", bg = "background_dark" }
|
||||
"ui.selection" = { bg = "secondary_highlight" }
|
||||
"ui.selection.primary" = { bg = "primary_highlight" }
|
||||
"ui.cursorline" = { bg = "background_dark" }
|
||||
"ui.statusline" = { fg = "foreground", bg = "background_dark" }
|
||||
"ui.statusline.inactive" = { fg = "comment", bg = "background_dark" }
|
||||
"ui.statusline.insert" = { fg = "olive", bg = "background_dark" }
|
||||
@ -71,49 +71,54 @@
|
||||
"ui.statusline.select" = { fg = "carnation", bg = "background_dark" }
|
||||
"ui.text" = { fg = "foreground" }
|
||||
"ui.text.focus" = { fg = "cyan" }
|
||||
"ui.window" = { fg = "foreground" }
|
||||
"ui.virtual.whitespace" = { fg = "comment" }
|
||||
"ui.virtual.indent-guide" = { fg = "opal" }
|
||||
"ui.virtual.ruler" = { bg = "background_dark" }
|
||||
"ui.virtual.whitespace" = { fg = "comment" }
|
||||
"ui.window" = { fg = "foreground" }
|
||||
|
||||
"error" = { fg = "red" }
|
||||
"warning" = { fg = "cyan" }
|
||||
|
||||
"diagnostic.unnecessary" = { modifiers = ["dim"] }
|
||||
"diagnostic" = { underline = { style = "line", color = "coral" }, bg = "cyan" }
|
||||
"diagnostic.deprecated" = { modifiers = ["crossed_out"] }
|
||||
"diagnostic.error" = { underline = { style = "curl", color = "red" } }
|
||||
"diagnostic.hint" = { underline = { style = "line", color = "cyan" } }
|
||||
"diagnostic.info" = { underline = { style = "line" } }
|
||||
"diagnostic.unnecessary" = { modifiers = ["dim"] }
|
||||
"diagnostic.warning" = { underline = { style = "curl", color = "yellow" } }
|
||||
|
||||
"markup.heading" = { fg = "purple", modifiers = ["bold"] }
|
||||
"markup.link.label" = { fg = "blue", modifiers = ["italic"] }
|
||||
"markup.list" = "cyan"
|
||||
"markup.bold" = { fg = "blue", modifiers = ["bold"] }
|
||||
"markup.heading" = { fg = "purple", modifiers = ["bold"] }
|
||||
"markup.italic" = { fg = "yellow", modifiers = ["italic"] }
|
||||
"markup.strikethrough" = { modifiers = ["crossed_out"] }
|
||||
"markup.link.url" = "cyan"
|
||||
"markup.link.label" = { fg = "blue", modifiers = ["italic"] }
|
||||
"markup.link.text" = "magenta"
|
||||
"markup.link.url" = "cyan"
|
||||
"markup.list" = "cyan"
|
||||
"markup.quote" = { fg = "yellow", modifiers = ["italic"] }
|
||||
"markup.raw" = { fg = "foreground" }
|
||||
"markup.strikethrough" = { modifiers = ["crossed_out"] }
|
||||
|
||||
[palette]
|
||||
background = "#282a36"
|
||||
background_dark = "#21222c"
|
||||
comment = "#a39e9b"
|
||||
foreground = "#eff0eb"
|
||||
primary_highlight = "#800049"
|
||||
secondary_highlight = "#4d4f66"
|
||||
foreground = "#eff0eb"
|
||||
comment = "#a39e9b"
|
||||
|
||||
# main colors
|
||||
red = "#ff5c57"
|
||||
blue = "#57c7ff"
|
||||
yellow = "#f3f99d"
|
||||
green = "#5af78e"
|
||||
purple = "#bd93f9"
|
||||
cyan = "#9aedfe"
|
||||
green = "#5af78e"
|
||||
magenta = "#ff6ac1"
|
||||
purple = "#bd93f9"
|
||||
red = "#ff5c57"
|
||||
yellow = "#f3f99d"
|
||||
|
||||
# aux colors
|
||||
lilac = "#c9c5fb"
|
||||
coral = "#f97c7c"
|
||||
sand = "#ffab6f"
|
||||
carnation = "#f99fc6"
|
||||
coral = "#f97c7c"
|
||||
lilac = "#c9c5fb"
|
||||
olive = "#b6d37c"
|
||||
opal = "#b1d7c7"
|
||||
sand = "#ffab6f"
|
||||
|
@ -61,7 +61,8 @@
|
||||
"ui.cursor" = { fg = "white", modifiers = ["reversed"] }
|
||||
"ui.cursor.primary" = { fg = "white", modifiers = ["reversed"] }
|
||||
"ui.cursor.match" = { fg = "blue", modifiers = ["underlined"] }
|
||||
"ui.cursor.insert" = { fg = "dark-blue" }
|
||||
# Malformed ANSI: dark-blue. See 'https://github.com/helix-editor/helix/issues/5709'
|
||||
# "ui.cursor.insert" = { fg = "dark-blue" }
|
||||
|
||||
"ui.selection" = { bg = "faint-gray" }
|
||||
"ui.selection.primary" = { bg = "#293b5bff" }
|
||||
|
@ -1462,7 +1462,7 @@ letters! that is not good grammar. you can fix this.
|
||||
|
||||
Still from hello2, press Ctrl-w H to swap with the split on the
|
||||
left: now hello2 is on the left and the tutor is on the top
|
||||
right. After Ctrl-w you can use HJKL to split with the buffer
|
||||
right. After Ctrl-w you can use HJKL to swap with the buffer
|
||||
on the left / below / above / on the right.
|
||||
|
||||
Move back to the tutor split, and press Ctrl-w o to only keep
|
||||
|
@ -2,6 +2,7 @@
|
||||
mod helpers;
|
||||
mod path;
|
||||
mod querycheck;
|
||||
mod theme_check;
|
||||
|
||||
use std::{env, error::Error};
|
||||
|
||||
@ -11,6 +12,7 @@ pub mod tasks {
|
||||
use crate::docgen::{lang_features, typable_commands, write};
|
||||
use crate::docgen::{LANG_SUPPORT_MD_OUTPUT, TYPABLE_COMMANDS_MD_OUTPUT};
|
||||
use crate::querycheck::query_check;
|
||||
use crate::theme_check::theme_check;
|
||||
use crate::DynError;
|
||||
|
||||
pub fn docgen() -> Result<(), DynError> {
|
||||
@ -23,6 +25,10 @@ pub fn querycheck() -> Result<(), DynError> {
|
||||
query_check()
|
||||
}
|
||||
|
||||
pub fn themecheck() -> Result<(), DynError> {
|
||||
theme_check()
|
||||
}
|
||||
|
||||
pub fn print_help() {
|
||||
println!(
|
||||
"
|
||||
@ -43,6 +49,7 @@ fn main() -> Result<(), DynError> {
|
||||
Some(t) => match t.as_str() {
|
||||
"docgen" => tasks::docgen()?,
|
||||
"query-check" => tasks::querycheck()?,
|
||||
"theme-check" => tasks::themecheck()?,
|
||||
invalid => return Err(format!("Invalid task name: {}", invalid).into()),
|
||||
},
|
||||
};
|
||||
|
@ -11,8 +11,16 @@ pub fn book_gen() -> PathBuf {
|
||||
project_root().join("book/src/generated/")
|
||||
}
|
||||
|
||||
pub fn runtime() -> PathBuf {
|
||||
project_root().join("runtime")
|
||||
}
|
||||
|
||||
pub fn ts_queries() -> PathBuf {
|
||||
project_root().join("runtime/queries")
|
||||
runtime().join("queries")
|
||||
}
|
||||
|
||||
pub fn themes() -> PathBuf {
|
||||
runtime().join("themes")
|
||||
}
|
||||
|
||||
pub fn lang_config() -> PathBuf {
|
||||
|
33
xtask/src/theme_check.rs
Normal file
33
xtask/src/theme_check.rs
Normal file
@ -0,0 +1,33 @@
|
||||
use helix_view::theme::Loader;
|
||||
|
||||
use crate::{path, DynError};
|
||||
|
||||
pub fn theme_check() -> Result<(), DynError> {
|
||||
let theme_names = [
|
||||
vec!["default".to_string(), "base16_default".to_string()],
|
||||
Loader::read_names(&path::themes()),
|
||||
]
|
||||
.concat();
|
||||
let loader = Loader::new(&[path::runtime()]);
|
||||
let mut errors_present = false;
|
||||
|
||||
for name in theme_names {
|
||||
let (_, warnings) = loader.load_with_warnings(&name).unwrap();
|
||||
|
||||
if !warnings.is_empty() {
|
||||
errors_present = true;
|
||||
println!("Theme '{name}' loaded with errors:");
|
||||
for warning in warnings {
|
||||
println!("\t* {}", warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match errors_present {
|
||||
true => Err("Errors found when loading bundled themes".into()),
|
||||
false => {
|
||||
println!("Theme check successful!");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user