diff --git a/.gitignore b/.gitignore
index 6a6fc782a..64a837dfd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@ target
helix-term/rustfmt.toml
result
runtime/grammars
+.DS_Store
diff --git a/Cargo.lock b/Cargo.lock
index 86dace16c..f4f03c797 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -136,9 +136,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]]
name = "cc"
-version = "1.1.37"
+version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40545c26d092346d8a8dab71ee48e7685a7a9cba76e634790c215b41a4a7b4cf"
+checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47"
dependencies = [
"shlex",
]
@@ -383,7 +383,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e"
dependencies = [
"libc",
- "thiserror",
+ "thiserror 1.0.69",
"winapi",
]
@@ -522,7 +522,7 @@ dependencies = [
"gix-worktree",
"once_cell",
"smallvec",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -535,7 +535,7 @@ dependencies = [
"gix-date",
"gix-utils",
"itoa",
- "thiserror",
+ "thiserror 1.0.69",
"winnow",
]
@@ -552,7 +552,7 @@ dependencies = [
"gix-trace",
"kstring",
"smallvec",
- "thiserror",
+ "thiserror 1.0.69",
"unicode-bom",
]
@@ -562,7 +562,7 @@ version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f78312288bd02052be5dbc2ecbc342c9f4eb791986d86c0a5c06b92dc72efa"
dependencies = [
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -571,7 +571,7 @@ version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c28b58ba04f0c004722344390af9dbc85888fbb84be1981afb934da4114d4cf"
dependencies = [
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -597,7 +597,7 @@ dependencies = [
"gix-features",
"gix-hash",
"memmap2",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -616,7 +616,7 @@ dependencies = [
"memchr",
"once_cell",
"smallvec",
- "thiserror",
+ "thiserror 1.0.69",
"unicode-bom",
"winnow",
]
@@ -631,7 +631,7 @@ dependencies = [
"bstr",
"gix-path",
"libc",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -643,7 +643,7 @@ dependencies = [
"bstr",
"itoa",
"jiff",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -664,7 +664,7 @@ dependencies = [
"gix-traverse",
"gix-worktree",
"imara-diff",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -684,7 +684,7 @@ dependencies = [
"gix-trace",
"gix-utils",
"gix-worktree",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -700,7 +700,7 @@ dependencies = [
"gix-path",
"gix-ref",
"gix-sec",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -718,7 +718,7 @@ dependencies = [
"once_cell",
"prodash",
"sha1_smol",
- "thiserror",
+ "thiserror 1.0.69",
"walkdir",
]
@@ -740,7 +740,7 @@ dependencies = [
"gix-trace",
"gix-utils",
"smallvec",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -773,7 +773,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "952c3a29f1bc1007cc901abce7479943abfa42016db089de33d0a4fa3c85bfe8"
dependencies = [
"faster-hex",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -825,7 +825,7 @@ dependencies = [
"memmap2",
"rustix",
"smallvec",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -836,7 +836,7 @@ checksum = "5102acdf4acae2644e38dbbd18cdfba9597a218f7d85f810fe5430207e03c2de"
dependencies = [
"gix-tempfile",
"gix-utils",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -855,7 +855,7 @@ dependencies = [
"gix-validate",
"itoa",
"smallvec",
- "thiserror",
+ "thiserror 1.0.69",
"winnow",
]
@@ -877,7 +877,7 @@ dependencies = [
"gix-quote",
"parking_lot",
"tempfile",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -895,7 +895,7 @@ dependencies = [
"gix-path",
"memmap2",
"smallvec",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -907,7 +907,7 @@ dependencies = [
"bstr",
"faster-hex",
"gix-trace",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -920,7 +920,7 @@ dependencies = [
"gix-trace",
"home",
"once_cell",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -935,7 +935,7 @@ dependencies = [
"gix-config-value",
"gix-glob",
"gix-path",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -946,7 +946,7 @@ checksum = "f89f9a1525dcfd9639e282ea939f5ab0d09d93cf2b90c1fc6104f1b9582a8e49"
dependencies = [
"bstr",
"gix-utils",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -966,7 +966,7 @@ dependencies = [
"gix-utils",
"gix-validate",
"memmap2",
- "thiserror",
+ "thiserror 1.0.69",
"winnow",
]
@@ -981,7 +981,7 @@ dependencies = [
"gix-revision",
"gix-validate",
"smallvec",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -996,7 +996,7 @@ dependencies = [
"gix-hash",
"gix-object",
"gix-revwalk",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -1011,7 +1011,7 @@ dependencies = [
"gix-hashtable",
"gix-object",
"smallvec",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -1046,7 +1046,7 @@ dependencies = [
"gix-pathspec",
"gix-worktree",
"portable-atomic",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -1061,7 +1061,7 @@ dependencies = [
"gix-pathspec",
"gix-refspec",
"gix-url",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -1098,7 +1098,7 @@ dependencies = [
"gix-object",
"gix-revwalk",
"smallvec",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -1110,7 +1110,7 @@ dependencies = [
"bstr",
"gix-features",
"gix-path",
- "thiserror",
+ "thiserror 1.0.69",
"url",
]
@@ -1132,7 +1132,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e187b263461bc36cea17650141567753bc6207d036cedd1de6e81a52f277ff68"
dependencies = [
"bstr",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -1263,7 +1263,7 @@ dependencies = [
"log",
"serde",
"serde_json",
- "thiserror",
+ "thiserror 2.0.3",
"tokio",
]
@@ -1319,7 +1319,7 @@ dependencies = [
"serde",
"serde_json",
"slotmap",
- "thiserror",
+ "thiserror 2.0.3",
"tokio",
"tokio-stream",
]
@@ -1392,7 +1392,7 @@ dependencies = [
"smallvec",
"tempfile",
"termini",
- "thiserror",
+ "thiserror 2.0.3",
"tokio",
"tokio-stream",
"toml",
@@ -1459,7 +1459,7 @@ dependencies = [
"serde_json",
"slotmap",
"tempfile",
- "thiserror",
+ "thiserror 2.0.3",
"tokio",
"tokio-stream",
"toml",
@@ -1755,9 +1755,9 @@ dependencies = [
[[package]]
name = "libc"
-version = "0.2.162"
+version = "0.2.164"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
+checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
[[package]]
name = "libloading"
@@ -1911,9 +1911,9 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "open"
-version = "5.3.0"
+version = "5.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61a877bf6abd716642a53ef1b89fb498923a4afca5c754f9050b4d081c05c4b3"
+checksum = "3ecd52f0b8d15c40ce4820aa251ed5de032e5d91fab27f7db2f40d42a8bdf69c"
dependencies = [
"is-wsl",
"libc",
@@ -2128,9 +2128,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustix"
-version = "0.38.40"
+version = "0.38.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0"
+checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6"
dependencies = [
"bitflags",
"errno",
@@ -2182,9 +2182,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.132"
+version = "1.0.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
+checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
dependencies = [
"itoa",
"memchr",
@@ -2398,18 +2398,38 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "1.0.64"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa"
+dependencies = [
+ "thiserror-impl 2.0.3",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
- "thiserror-impl",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.64"
+version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
+checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568"
dependencies = [
"proc-macro2",
"quote",
@@ -2551,9 +2571,9 @@ checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217"
[[package]]
name = "unicode-general-category"
-version = "0.6.0"
+version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2281c8c1d221438e373249e065ca4989c4c36952c211ff21a0ee91c44a3869e7"
+checksum = "24adfe8311434967077a6adff125729161e6e4934d76f6b7c55318ac5c9246d3"
[[package]]
name = "unicode-ident"
@@ -2691,9 +2711,9 @@ checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
[[package]]
name = "which"
-version = "6.0.3"
+version = "7.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f"
+checksum = "c9cad3279ade7346b96e38731a641d7343dd6a53d55083dd54eadfa5a1b38c6b"
dependencies = [
"either",
"home",
diff --git a/Cargo.toml b/Cargo.toml
index 763992480..b690267d9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -41,7 +41,7 @@ package.helix-term.opt-level = 2
tree-sitter = { version = "0.22" }
nucleo = "0.5.0"
slotmap = "1.0.7"
-thiserror = "1.0"
+thiserror = "2.0"
[workspace.package]
version = "24.7.0"
diff --git a/book/src/building-from-source.md b/book/src/building-from-source.md
index 42ed57a27..539e9cf86 100644
--- a/book/src/building-from-source.md
+++ b/book/src/building-from-source.md
@@ -117,7 +117,7 @@ to package the runtime into `/usr/lib/helix/runtime`. The rough steps a build
script could follow are:
1. `export HELIX_DEFAULT_RUNTIME=/usr/lib/helix/runtime`
-1. `cargo build --profile opt --locked --path helix-term`
+1. `cargo build --profile opt --locked`
1. `cp -r runtime $BUILD_DIR/usr/lib/helix/`
1. `cp target/opt/hx $BUILD_DIR/usr/bin/hx`
diff --git a/book/src/configuration.md b/book/src/configuration.md
index 0cd12568b..317007efc 100644
--- a/book/src/configuration.md
+++ b/book/src/configuration.md
@@ -27,8 +27,8 @@ hidden = false
You can use a custom configuration file by specifying it with the `-c` or
`--config` command line argument, for example `hx -c path/to/custom-config.toml`.
-Additionally, you can reload the configuration file by sending the USR1
-signal to the Helix process on Unix operating systems, such as by using the command `pkill -USR1 hx`.
+You can reload the config file by issuing the `:config-reload` command. Alternatively, on Unix operating systems, you can reload it by sending the USR1
+signal to the Helix process, such as by using the command `pkill -USR1 hx`.
Finally, you can have a `config.toml` local to a project by putting it under a `.helix` directory in your repository.
Its settings will be merged with the configuration directory `config.toml` and the built-in configuration.
diff --git a/book/src/editor.md b/book/src/editor.md
index 82d5f8461..fa5aef47e 100644
--- a/book/src/editor.md
+++ b/book/src/editor.md
@@ -24,6 +24,7 @@
|--|--|---------|
| `scrolloff` | Number of lines of padding around the edge of the screen when scrolling | `5` |
| `mouse` | Enable mouse mode | `true` |
+| `default-yank-register` | Default register used for yank/paste | `"` |
| `middle-click-paste` | Middle click paste support | `true` |
| `scroll-lines` | Number of lines to scroll per scroll wheel step | `3` |
| `shell` | Shell to use when running external commands | Unix: `["sh", "-c"]`
Windows: `["cmd", "/C"]` |
@@ -52,6 +53,30 @@
| `indent-heuristic` | How the indentation for a newly inserted line is computed: `simple` just copies the indentation level from the previous line, `tree-sitter` computes the indentation based on the syntax tree and `hybrid` combines both approaches. If the chosen heuristic is not available, a different one will be used as a fallback (the fallback order being `hybrid` -> `tree-sitter` -> `simple`). | `hybrid`
| `jump-label-alphabet` | The characters that are used to generate two character jump labels. Characters at the start of the alphabet are used first. | `"abcdefghijklmnopqrstuvwxyz"`
| `end-of-line-diagnostics` | Minimum severity of diagnostics to render at the end of the line. Set to `disable` to disable entirely. Refer to the setting about `inline-diagnostics` for more details | "disable"
+| `clipboard-provider` | Which API to use for clipboard interaction. One of `pasteboard` (MacOS), `wayland`, `x-clip`, `x-sel`, `win-32-yank`, `termux`, `tmux`, `windows`, `termcode`, `none`, or a custom command set. | Platform and environment specific. |
+
+### `[editor.clipboard-provider]` Section
+
+Helix can be configured wither to use a builtin clipboard configuration or to use
+a provided command.
+
+For instance, setting it to use OSC 52 termcodes, the configuration would be:
+```toml
+[editor]
+clipboard-provider = "termcode"
+```
+
+Alternatively, Helix can be configured to use arbitary commands for clipboard integration:
+
+```toml
+[editor.clipboard-provider.custom]
+yank = { command = "cat", args = ["test.txt"] }
+paste = { command = "tee", args = ["test.txt"] }
+primary-yank = { command = "cat", args = ["test-primary.txt"] } # optional
+primary-paste = { command = "tee", args = ["test-primary.txt"] } # optional
+```
+
+For custom commands the contents of the yank/paste is communicated over stdin/stdout.
### `[editor.statusline]` Section
@@ -428,7 +453,8 @@ fn main() {
The new diagnostic rendering is not yet enabled by default. As soon as end of line or inline diagnostics are enabled the old diagnostics rendering is automatically disabled. The recommended default setting are:
-```
+```toml
+[editor]
end-of-line-diagnostics = "hint"
[editor.inline-diagnostics]
cursor-line = "warning" # show warnings and errors on the cursorline inline
diff --git a/book/src/generated/lang-support.md b/book/src/generated/lang-support.md
index 09f15b374..28f25ba3f 100644
--- a/book/src/generated/lang-support.md
+++ b/book/src/generated/lang-support.md
@@ -91,7 +91,7 @@
| hosts | ✓ | | | |
| html | ✓ | | | `vscode-html-language-server`, `superhtml` |
| hurl | ✓ | ✓ | ✓ | |
-| hyprlang | ✓ | | ✓ | |
+| hyprlang | ✓ | | ✓ | `hyprls` |
| idris | | | | `idris2-lsp` |
| iex | ✓ | | | |
| ini | ✓ | | | |
@@ -136,6 +136,7 @@
| move | ✓ | | | |
| msbuild | ✓ | | ✓ | |
| nasm | ✓ | ✓ | | |
+| nestedtext | ✓ | ✓ | ✓ | |
| nickel | ✓ | | ✓ | `nls` |
| nim | ✓ | ✓ | ✓ | `nimlangserver` |
| nix | ✓ | ✓ | | `nil`, `nixd` |
@@ -168,6 +169,7 @@
| purescript | ✓ | ✓ | | `purescript-language-server` |
| python | ✓ | ✓ | ✓ | `ruff`, `jedi-language-server`, `pylsp` |
| qml | ✓ | | ✓ | `qmlls` |
+| quint | ✓ | | | `quint-language-server` |
| r | ✓ | | | `R` |
| racket | ✓ | | ✓ | `racket` |
| regex | ✓ | | | |
@@ -189,6 +191,7 @@
| sml | ✓ | | | |
| snakemake | ✓ | | ✓ | `pylsp` |
| solidity | ✓ | ✓ | | `solc` |
+| spade | ✓ | | ✓ | `spade-language-server` |
| spicedb | ✓ | | | |
| sql | ✓ | ✓ | | |
| sshclientconfig | ✓ | | | |
@@ -204,6 +207,7 @@
| task | ✓ | | | |
| tcl | ✓ | | ✓ | |
| templ | ✓ | | | `templ` |
+| textproto | ✓ | ✓ | ✓ | |
| tfvars | ✓ | | ✓ | `terraform-ls` |
| thrift | ✓ | | | |
| todotxt | ✓ | | | |
@@ -215,7 +219,7 @@
| typespec | ✓ | ✓ | ✓ | `tsp-server` |
| typst | ✓ | | | `tinymist`, `typst-lsp` |
| ungrammar | ✓ | | | |
-| unison | ✓ | | ✓ | |
+| unison | ✓ | ✓ | ✓ | |
| uxntal | ✓ | | | |
| v | ✓ | ✓ | ✓ | `v-analyzer` |
| vala | ✓ | ✓ | | `vala-language-server` |
diff --git a/book/src/keymap.md b/book/src/keymap.md
index 71ae5e31f..28113ea0b 100644
--- a/book/src/keymap.md
+++ b/book/src/keymap.md
@@ -112,42 +112,43 @@ Normal mode is the default mode when you launch helix. You can return to it from
### Selection manipulation
-| Key | Description | Command |
-| ----- | ----------- | ------- |
-| `s` | Select all regex matches inside selections | `select_regex` |
-| `S` | Split selection into sub selections on regex matches | `split_selection` |
-| `Alt-s` | Split selection on newlines | `split_selection_on_newline` |
-| `Alt-minus` | Merge selections | `merge_selections` |
-| `Alt-_` | Merge consecutive selections | `merge_consecutive_selections` |
-| `&` | Align selection in columns | `align_selections` |
-| `_` | Trim whitespace from the selection | `trim_selections` |
-| `;` | Collapse selection onto a single cursor | `collapse_selection` |
-| `Alt-;` | Flip selection cursor and anchor | `flip_selections` |
-| `Alt-:` | Ensures the selection is in forward direction | `ensure_selections_forward` |
-| `,` | Keep only the primary selection | `keep_primary_selection` |
-| `Alt-,` | Remove the primary selection | `remove_primary_selection` |
-| `C` | Copy selection onto the next line (Add cursor below) | `copy_selection_on_next_line` |
-| `Alt-C` | Copy selection onto the previous line (Add cursor above) | `copy_selection_on_prev_line` |
-| `(` | Rotate main selection backward | `rotate_selections_backward` |
-| `)` | Rotate main selection forward | `rotate_selections_forward` |
-| `Alt-(` | Rotate selection contents backward | `rotate_selection_contents_backward` |
-| `Alt-)` | Rotate selection contents forward | `rotate_selection_contents_forward` |
-| `%` | Select entire file | `select_all` |
-| `x` | Select current line, if already selected, extend to next line | `extend_line_below` |
-| `X` | Extend selection to line bounds (line-wise selection) | `extend_to_line_bounds` |
-| `Alt-x` | Shrink selection to line bounds (line-wise selection) | `shrink_to_line_bounds` |
-| `J` | Join lines inside selection | `join_selections` |
-| `Alt-J` | Join lines inside selection and select the inserted space | `join_selections_space` |
-| `K` | Keep selections matching the regex | `keep_selections` |
-| `Alt-K` | Remove selections matching the regex | `remove_selections` |
-| `Ctrl-c` | Comment/uncomment the selections | `toggle_comments` |
-| `Alt-o`, `Alt-up` | Expand selection to parent syntax node (**TS**) | `expand_selection` |
-| `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` |
+| Key | Description | Command |
+| ----- | ----------- | ------- |
+| `s` | Select all regex matches inside selections | `select_regex` |
+| `S` | Split selection into sub selections on regex matches | `split_selection` |
+| `Alt-s` | Split selection on newlines | `split_selection_on_newline` |
+| `Alt-minus` | Merge selections | `merge_selections` |
+| `Alt-_` | Merge consecutive selections | `merge_consecutive_selections` |
+| `&` | Align selection in columns | `align_selections` |
+| `_` | Trim whitespace from the selection | `trim_selections` |
+| `;` | Collapse selection onto a single cursor | `collapse_selection` |
+| `Alt-;` | Flip selection cursor and anchor | `flip_selections` |
+| `Alt-:` | Ensures the selection is in forward direction | `ensure_selections_forward` |
+| `,` | Keep only the primary selection | `keep_primary_selection` |
+| `Alt-,` | Remove the primary selection | `remove_primary_selection` |
+| `C` | Copy selection onto the next line (Add cursor below) | `copy_selection_on_next_line` |
+| `Alt-C` | Copy selection onto the previous line (Add cursor above) | `copy_selection_on_prev_line` |
+| `(` | Rotate main selection backward | `rotate_selections_backward` |
+| `)` | Rotate main selection forward | `rotate_selections_forward` |
+| `Alt-(` | Rotate selection contents backward | `rotate_selection_contents_backward` |
+| `Alt-)` | Rotate selection contents forward | `rotate_selection_contents_forward` |
+| `%` | Select entire file | `select_all` |
+| `x` | Select current line, if already selected, extend to next line | `extend_line_below` |
+| `X` | Extend selection to line bounds (line-wise selection) | `extend_to_line_bounds` |
+| `Alt-x` | Shrink selection to line bounds (line-wise selection) | `shrink_to_line_bounds` |
+| `J` | Join lines inside selection | `join_selections` |
+| `Alt-J` | Join lines inside selection and select the inserted space | `join_selections_space` |
+| `K` | Keep selections matching the regex | `keep_selections` |
+| `Alt-K` | Remove selections matching the regex | `remove_selections` |
+| `Ctrl-c` | Comment/uncomment the selections | `toggle_comments` |
+| `Alt-o`, `Alt-up` | Expand selection to parent syntax node (**TS**) | `expand_selection` |
+| `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-I`, `Alt-Shift-down`| Select all children nodes in syntax tree (**TS**) | `select_all_children` |
+| `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
@@ -281,7 +282,7 @@ This layer is a kludge of mappings, mostly pickers.
| Key | Description | Command |
| ----- | ----------- | ------- |
-| `f` | Open file picker | `file_picker` |
+| `f` | Open file picker at LSP workspace root | `file_picker` |
| `F` | Open file picker at current working directory | `file_picker_in_current_directory` |
| `b` | Open buffer picker | `buffer_picker` |
| `j` | Open jumplist picker | `jumplist_picker` |
diff --git a/book/src/package-managers.md b/book/src/package-managers.md
index 441de45e0..a08baccd1 100644
--- a/book/src/package-managers.md
+++ b/book/src/package-managers.md
@@ -101,7 +101,15 @@ Download the official Helix AppImage from the [latest releases](https://github.c
chmod +x helix-*.AppImage # change permission for executable mode
./helix-*.AppImage # run helix
```
-
+
+You can optionally [add the `.desktop` file](./building-from-source.md#configure-the-desktop-shortcut). Helix must be installed in `PATH` with the name `hx`. For example:
+```sh
+mkdir -p "$HOME/.local/bin"
+mv helix-*.AppImage "$HOME/.local/bin/hx"
+```
+
+and make sure `~/.local/bin` is in your `PATH`.
+
## macOS
### Homebrew Core
diff --git a/book/src/remapping.md b/book/src/remapping.md
index e3efdf16f..41e20f84b 100644
--- a/book/src/remapping.md
+++ b/book/src/remapping.md
@@ -4,10 +4,31 @@ Helix currently supports one-way key remapping through a simple TOML configurati
file. (More powerful solutions such as rebinding via commands will be
available in the future).
+There are three kinds of commands that can be used in keymaps:
+
+* Static commands: commands like `move_char_right` which are usually bound to
+ keys and used for movement and editing. A list of static commands is
+ available in the [Keymap](./keymap.html) documentation and in the source code
+ in [`helix-term/src/commands.rs`](https://github.com/helix-editor/helix/blob/master/helix-term/src/commands.rs)
+ at the invocation of `static_commands!` macro.
+* Typable commands: commands that can be executed from command mode (`:`), for
+ example `:write!`. See the [Commands](./commands.html) documentation for a
+ list of available typeable commands or the `TypableCommandList` declaration in
+ the source code at [`helix-term/src/commands/typed.rs`](https://github.com/helix-editor/helix/blob/master/helix-term/src/commands/typed.rs).
+* Macros: sequences of keys that are executed in order. These keybindings
+ start with `@` and then list any number of keys to be executed. For example
+ `@miw` can be used to select the surrounding word. For now, macro keybindings
+ are not allowed in keybinding sequences due to limitations in the way that
+ command sequences are executed. Modifier keys (e.g. Alt+o) can be used
+ like `""`, e.g. `"@miw"`
+
To remap keys, create a `config.toml` file in your `helix` configuration
directory (default `~/.config/helix` on Linux systems) with a structure like
this:
+> 💡 To set a modifier + key as a keymap, type `A-X = ...` or `C-X = ...` for Alt + X or Ctrl + X. Combine with Shift using a dash, e.g. `C-S-esc`.
+> Within macros, wrap them in `<>`, e.g. `` and `` to distinguish from the `A` or `C` keys.
+
```toml
# At most one section each of 'keys.normal', 'keys.insert' and 'keys.select'
[keys.normal]
@@ -18,6 +39,7 @@ w = "move_line_up" # Maps the 'w' key move_line_up
"C-S-esc" = "extend_line" # Maps Ctrl-Shift-Escape to extend_line
g = { a = "code_action" } # Maps `ga` to show possible code actions
"ret" = ["open_below", "normal_mode"] # Maps the enter key to open_below then re-enter normal mode
+"A-x" = "@x" # Maps Alt-x to a macro selecting the whole line and deleting it without yanking it
[keys.insert]
"A-x" = "normal_mode" # Maps Alt-X to enter normal mode
@@ -74,21 +96,3 @@ Ctrl, Shift and Alt modifiers are encoded respectively with the prefixes
| Escape | `"esc"` |
Keys can be disabled by binding them to the `no_op` command.
-
-## Commands
-
-There are three kinds of commands that can be used in keymaps:
-
-* Static commands: commands like `move_char_right` which are usually bound to
- keys and used for movement and editing. A list of static commands is
- available in the [Keymap](./keymap.html) documentation and in the source code
- in [`helix-term/src/commands.rs`](https://github.com/helix-editor/helix/blob/master/helix-term/src/commands.rs)
- at the invocation of `static_commands!` macro and the `TypableCommandList`.
-* Typable commands: commands that can be executed from command mode (`:`), for
- example `:write!`. See the [Commands](./commands.html) documentation for a
- list of available typeable commands.
-* Macros: sequences of keys that are executed in order. These keybindings
- start with `@` and then list any number of keys to be executed. For example
- `@miw` can be used to select the surrounding word. For now, macro keybindings
- are not allowed in keybinding sequences due to limitations in the way that
- command sequences are executed.
diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml
index db06f0081..d7f9d7206 100644
--- a/helix-core/Cargo.toml
+++ b/helix-core/Cargo.toml
@@ -29,7 +29,7 @@ unicode-segmentation = "1.12"
# For now lets lock the version to avoid rendering glitches
# when installing without `--locked`
unicode-width = "=0.1.12"
-unicode-general-category = "0.6"
+unicode-general-category = "1.0"
slotmap.workspace = true
tree-sitter.workspace = true
once_cell = "1.20"
diff --git a/helix-core/src/wrap.rs b/helix-core/src/wrap.rs
index f32d6f4bc..337b389ae 100644
--- a/helix-core/src/wrap.rs
+++ b/helix-core/src/wrap.rs
@@ -4,6 +4,8 @@ use textwrap::{Options, WordSplitter::NoHyphenation};
/// Given a slice of text, return the text re-wrapped to fit it
/// within the given width.
pub fn reflow_hard_wrap(text: &str, text_width: usize) -> SmartString {
- let options = Options::new(text_width).word_splitter(NoHyphenation);
+ let options = Options::new(text_width)
+ .word_splitter(NoHyphenation)
+ .word_separator(textwrap::WordSeparator::AsciiSpace);
textwrap::refill(text, options).into()
}
diff --git a/helix-lsp-types/Cargo.toml b/helix-lsp-types/Cargo.toml
index b44e4c93a..cb6b94307 100644
--- a/helix-lsp-types/Cargo.toml
+++ b/helix-lsp-types/Cargo.toml
@@ -23,7 +23,7 @@ license = "MIT"
[dependencies]
bitflags = "2.6.0"
serde = { version = "1.0.215", features = ["derive"] }
-serde_json = "1.0.132"
+serde_json = "1.0.133"
serde_repr = "0.1"
url = {version = "2.5.3", features = ["serde"]}
diff --git a/helix-stdx/Cargo.toml b/helix-stdx/Cargo.toml
index c4fbe5e78..4e85adb5b 100644
--- a/helix-stdx/Cargo.toml
+++ b/helix-stdx/Cargo.toml
@@ -15,7 +15,7 @@ homepage.workspace = true
dunce = "1.0"
etcetera = "0.8"
ropey = { version = "1.6.1", default-features = false }
-which = "6.0"
+which = "7.0"
regex-cursor = "0.1.4"
bitflags = "2.6"
diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml
index cd2cfe034..1c399a47c 100644
--- a/helix-term/Cargo.toml
+++ b/helix-term/Cargo.toml
@@ -56,10 +56,10 @@ ignore = "0.4"
pulldown-cmark = { version = "0.12", default-features = false }
# file type detection
content_inspector = "0.2.4"
-thiserror = "1.0"
+thiserror.workspace = true
# opening URLs
-open = "5.3.0"
+open = "5.3.1"
url = "2.5.3"
# config
@@ -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.162"
+libc = "0.2.164"
[target.'cfg(target_os = "macos")'.dependencies]
crossterm = { version = "0.28", features = ["event-stream", "use-dev-tty", "libc"] }
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index ee2949fa0..61855d356 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -2735,7 +2735,9 @@ fn delete_selection_impl(cx: &mut Context, op: Operation, yank: YankAction) {
// yank the selection
let text = doc.text().slice(..);
let values: Vec = selection.fragments(text).map(Cow::into_owned).collect();
- let reg_name = cx.register.unwrap_or('"');
+ let reg_name = cx
+ .register
+ .unwrap_or_else(|| cx.editor.config.load().default_yank_register);
if let Err(err) = cx.editor.registers.write(reg_name, values) {
cx.editor.set_error(err.to_string());
return;
@@ -4221,7 +4223,11 @@ fn commit_undo_checkpoint(cx: &mut Context) {
// Yank / Paste
fn yank(cx: &mut Context) {
- yank_impl(cx.editor, cx.register.unwrap_or('"'));
+ yank_impl(
+ cx.editor,
+ cx.register
+ .unwrap_or(cx.editor.config().default_yank_register),
+ );
exit_select_mode(cx);
}
@@ -4282,7 +4288,12 @@ fn yank_joined_impl(editor: &mut Editor, separator: &str, register: char) {
fn yank_joined(cx: &mut Context) {
let separator = doc!(cx.editor).line_ending.as_str();
- yank_joined_impl(cx.editor, separator, cx.register.unwrap_or('"'));
+ yank_joined_impl(
+ cx.editor,
+ separator,
+ cx.register
+ .unwrap_or(cx.editor.config().default_yank_register),
+ );
exit_select_mode(cx);
}
@@ -4339,6 +4350,10 @@ fn paste_impl(
return;
}
+ if mode == Mode::Insert {
+ doc.append_changes_to_history(view);
+ }
+
let repeat = std::iter::repeat(
// `values` is asserted to have at least one entry above.
values
@@ -4438,7 +4453,12 @@ fn paste_primary_clipboard_before(cx: &mut Context) {
}
fn replace_with_yanked(cx: &mut Context) {
- replace_with_yanked_impl(cx.editor, cx.register.unwrap_or('"'), cx.count());
+ replace_with_yanked_impl(
+ cx.editor,
+ cx.register
+ .unwrap_or(cx.editor.config().default_yank_register),
+ cx.count(),
+ );
exit_select_mode(cx);
}
@@ -4501,7 +4521,8 @@ fn paste(editor: &mut Editor, register: char, pos: Paste, count: usize) {
fn paste_after(cx: &mut Context) {
paste(
cx.editor,
- cx.register.unwrap_or('"'),
+ cx.register
+ .unwrap_or(cx.editor.config().default_yank_register),
Paste::After,
cx.count(),
);
@@ -4511,7 +4532,8 @@ fn paste_after(cx: &mut Context) {
fn paste_before(cx: &mut Context) {
paste(
cx.editor,
- cx.register.unwrap_or('"'),
+ cx.register
+ .unwrap_or(cx.editor.config().default_yank_register),
Paste::Before,
cx.count(),
);
@@ -5365,7 +5387,8 @@ fn insert_register(cx: &mut Context) {
cx.register = Some(ch);
paste(
cx.editor,
- cx.register.unwrap_or('"'),
+ cx.register
+ .unwrap_or(cx.editor.config().default_yank_register),
Paste::Cursor,
cx.count(),
);
diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs
index 68ba9bab5..7402a06f3 100644
--- a/helix-term/src/commands/typed.rs
+++ b/helix-term/src/commands/typed.rs
@@ -1074,7 +1074,7 @@ fn show_clipboard_provider(
}
cx.editor
- .set_status(cx.editor.registers.clipboard_provider_name().to_string());
+ .set_status(cx.editor.registers.clipboard_provider_name());
Ok(())
}
diff --git a/helix-term/src/health.rs b/helix-term/src/health.rs
index 0bbb5735c..e59fd74dc 100644
--- a/helix-term/src/health.rs
+++ b/helix-term/src/health.rs
@@ -1,10 +1,10 @@
+use crate::config::{Config, ConfigLoadError};
use crossterm::{
style::{Color, Print, Stylize},
tty::IsTty,
};
use helix_core::config::{default_lang_config, user_lang_config};
use helix_loader::grammar::load_runtime_file;
-use helix_view::clipboard::get_clipboard_provider;
use std::io::Write;
#[derive(Copy, Clone)]
@@ -53,7 +53,6 @@ pub fn general() -> std::io::Result<()> {
let lang_file = helix_loader::lang_config_file();
let log_file = helix_loader::log_file();
let rt_dirs = helix_loader::runtime_dirs();
- let clipboard_provider = get_clipboard_provider();
if config_file.exists() {
writeln!(stdout, "Config file: {}", config_file.display())?;
@@ -92,7 +91,6 @@ pub fn general() -> std::io::Result<()> {
writeln!(stdout, "{}", msg.yellow())?;
}
}
- writeln!(stdout, "Clipboard provider: {}", clipboard_provider.name())?;
Ok(())
}
@@ -101,8 +99,19 @@ pub fn clipboard() -> std::io::Result<()> {
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
- let board = get_clipboard_provider();
- match board.name().as_ref() {
+ let config = match Config::load_default() {
+ Ok(config) => config,
+ Err(ConfigLoadError::Error(err)) if err.kind() == std::io::ErrorKind::NotFound => {
+ Config::default()
+ }
+ Err(err) => {
+ writeln!(stdout, "{}", "Configuration file malformed".red())?;
+ writeln!(stdout, "{}", err)?;
+ return Ok(());
+ }
+ };
+
+ match config.editor.clipboard_provider.name().as_ref() {
"none" => {
writeln!(
stdout,
diff --git a/helix-view/src/clipboard.rs b/helix-view/src/clipboard.rs
index 379accc7e..5e16461e0 100644
--- a/helix-view/src/clipboard.rs
+++ b/helix-view/src/clipboard.rs
@@ -1,356 +1,224 @@
// Implementation reference: https://github.com/neovim/neovim/blob/f2906a4669a2eef6d7bf86a29648793d63c98949/runtime/autoload/provider/clipboard.vim#L68-L152
-use anyhow::Result;
+use serde::{Deserialize, Serialize};
use std::borrow::Cow;
+use thiserror::Error;
-#[derive(Clone, Copy, Debug)]
+#[derive(Clone, Copy)]
pub enum ClipboardType {
Clipboard,
Selection,
}
-pub trait ClipboardProvider: std::fmt::Debug {
- fn name(&self) -> Cow;
- fn get_contents(&self, clipboard_type: ClipboardType) -> Result;
- fn set_contents(&mut self, contents: String, clipboard_type: ClipboardType) -> Result<()>;
+#[derive(Debug, Error)]
+pub enum ClipboardError {
+ #[error(transparent)]
+ IoError(#[from] std::io::Error),
+ #[error("could not convert terminal output to UTF-8: {0}")]
+ FromUtf8Error(#[from] std::string::FromUtf8Error),
+ #[cfg(windows)]
+ #[error("Windows API error: {0}")]
+ WinAPI(#[from] clipboard_win::ErrorCode),
+ #[error("clipboard provider command failed")]
+ CommandFailed,
+ #[error("failed to write to clipboard provider's stdin")]
+ StdinWriteFailed,
+ #[error("clipboard provider did not return any contents")]
+ MissingStdout,
+ #[error("This clipboard provider does not support reading")]
+ ReadingNotSupported,
}
-#[cfg(not(windows))]
-macro_rules! command_provider {
- (paste => $get_prg:literal $( , $get_arg:literal )* ; copy => $set_prg:literal $( , $set_arg:literal )* ; ) => {{
- log::debug!(
- "Using {} to interact with the system clipboard",
- if $set_prg != $get_prg { format!("{}+{}", $set_prg, $get_prg)} else { $set_prg.to_string() }
- );
- Box::new(provider::command::Provider {
- get_cmd: provider::command::Config {
- prg: $get_prg,
- args: &[ $( $get_arg ),* ],
- },
- set_cmd: provider::command::Config {
- prg: $set_prg,
- args: &[ $( $set_arg ),* ],
- },
- get_primary_cmd: None,
- set_primary_cmd: None,
- })
- }};
-
- (paste => $get_prg:literal $( , $get_arg:literal )* ;
- copy => $set_prg:literal $( , $set_arg:literal )* ;
- primary_paste => $pr_get_prg:literal $( , $pr_get_arg:literal )* ;
- primary_copy => $pr_set_prg:literal $( , $pr_set_arg:literal )* ;
- ) => {{
- log::debug!(
- "Using {} to interact with the system and selection (primary) clipboard",
- if $set_prg != $get_prg { format!("{}+{}", $set_prg, $get_prg)} else { $set_prg.to_string() }
- );
- Box::new(provider::command::Provider {
- get_cmd: provider::command::Config {
- prg: $get_prg,
- args: &[ $( $get_arg ),* ],
- },
- set_cmd: provider::command::Config {
- prg: $set_prg,
- args: &[ $( $set_arg ),* ],
- },
- get_primary_cmd: Some(provider::command::Config {
- prg: $pr_get_prg,
- args: &[ $( $pr_get_arg ),* ],
- }),
- set_primary_cmd: Some(provider::command::Config {
- prg: $pr_set_prg,
- args: &[ $( $pr_set_arg ),* ],
- }),
- })
- }};
-}
-
-#[cfg(windows)]
-pub fn get_clipboard_provider() -> Box {
- Box::::default()
-}
-
-#[cfg(target_os = "macos")]
-pub fn get_clipboard_provider() -> Box {
- use helix_stdx::env::{binary_exists, env_var_is_set};
-
- if env_var_is_set("TMUX") && binary_exists("tmux") {
- command_provider! {
- paste => "tmux", "save-buffer", "-";
- copy => "tmux", "load-buffer", "-w", "-";
- }
- } else if binary_exists("pbcopy") && binary_exists("pbpaste") {
- command_provider! {
- paste => "pbpaste";
- copy => "pbcopy";
- }
- } else {
- Box::new(provider::FallbackProvider::new())
- }
-}
+type Result = std::result::Result;
+#[cfg(not(target_arch = "wasm32"))]
+pub use external::ClipboardProvider;
#[cfg(target_arch = "wasm32")]
-pub fn get_clipboard_provider() -> Box {
- // TODO:
- Box::new(provider::FallbackProvider::new())
-}
+pub use noop::ClipboardProvider;
-#[cfg(not(any(windows, target_arch = "wasm32", target_os = "macos")))]
-pub fn get_clipboard_provider() -> Box {
- use helix_stdx::env::{binary_exists, env_var_is_set};
- use provider::command::is_exit_success;
- // TODO: support for user-defined provider, probably when we have plugin support by setting a
- // variable?
-
- if env_var_is_set("WAYLAND_DISPLAY") && binary_exists("wl-copy") && binary_exists("wl-paste") {
- command_provider! {
- paste => "wl-paste", "--no-newline";
- copy => "wl-copy", "--type", "text/plain";
- primary_paste => "wl-paste", "-p", "--no-newline";
- primary_copy => "wl-copy", "-p", "--type", "text/plain";
- }
- } else if env_var_is_set("DISPLAY") && binary_exists("xclip") {
- command_provider! {
- paste => "xclip", "-o", "-selection", "clipboard";
- copy => "xclip", "-i", "-selection", "clipboard";
- primary_paste => "xclip", "-o";
- primary_copy => "xclip", "-i";
- }
- } else if env_var_is_set("DISPLAY")
- && binary_exists("xsel")
- && is_exit_success("xsel", &["-o", "-b"])
- {
- // FIXME: check performance of is_exit_success
- command_provider! {
- paste => "xsel", "-o", "-b";
- copy => "xsel", "-i", "-b";
- primary_paste => "xsel", "-o";
- primary_copy => "xsel", "-i";
- }
- } else if binary_exists("win32yank.exe") {
- command_provider! {
- paste => "win32yank.exe", "-o", "--lf";
- copy => "win32yank.exe", "-i", "--crlf";
- }
- } else if binary_exists("termux-clipboard-set") && binary_exists("termux-clipboard-get") {
- command_provider! {
- paste => "termux-clipboard-get";
- copy => "termux-clipboard-set";
- }
- } else if env_var_is_set("TMUX") && binary_exists("tmux") {
- command_provider! {
- paste => "tmux", "save-buffer", "-";
- copy => "tmux", "load-buffer", "-w", "-";
- }
- } else {
- Box::new(provider::FallbackProvider::new())
- }
-}
+// Clipboard not supported for wasm
+#[cfg(target_arch = "wasm32")]
+mod noop {
+ use super::*;
-#[cfg(not(target_os = "windows"))]
-pub mod provider {
- use super::{ClipboardProvider, ClipboardType};
- use anyhow::Result;
- use std::borrow::Cow;
+ #[derive(Debug, Clone)]
+ pub enum ClipboardProvider {}
- #[cfg(feature = "term")]
- mod osc52 {
- use {super::ClipboardType, crate::base64};
+ impl ClipboardProvider {
+ pub fn detect() -> Self {
+ Self
+ }
- #[derive(Debug)]
- pub struct SetClipboardCommand {
- encoded_content: String,
- clipboard_type: ClipboardType,
+ pub fn name(&self) -> Cow {
+ "none".into()
}
- impl SetClipboardCommand {
- pub fn new(content: &str, clipboard_type: ClipboardType) -> Self {
- Self {
- encoded_content: base64::encode(content.as_bytes()),
- clipboard_type,
- }
- }
+ pub fn get_contents(&self, _clipboard_type: ClipboardType) -> Result {
+ Err(ClipboardError::ReadingNotSupported)
}
- impl crossterm::Command for SetClipboardCommand {
- fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
- let kind = match &self.clipboard_type {
- ClipboardType::Clipboard => "c",
- ClipboardType::Selection => "p",
- };
- // Send an OSC 52 set command: https://terminalguide.namepad.de/seq/osc-52/
- write!(f, "\x1b]52;{};{}\x1b\\", kind, &self.encoded_content)
- }
+ pub fn set_contents(&self, _content: &str, _clipboard_type: ClipboardType) -> Result<()> {
+ Ok(())
}
}
+}
- #[derive(Debug)]
- pub struct FallbackProvider {
- buf: String,
- primary_buf: String,
- }
+#[cfg(not(target_arch = "wasm32"))]
+mod external {
+ use super::*;
- impl FallbackProvider {
- pub fn new() -> Self {
- #[cfg(feature = "term")]
- log::debug!(
- "No native clipboard provider found. Yanking by OSC 52 and pasting will be internal to Helix"
- );
- #[cfg(not(feature = "term"))]
- log::warn!(
- "No native clipboard provider found! Yanking and pasting will be internal to Helix"
- );
- Self {
- buf: String::new(),
- primary_buf: String::new(),
- }
- }
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
+ pub struct Command {
+ command: Cow<'static, str>,
+ #[serde(default)]
+ args: Cow<'static, [Cow<'static, str>]>,
}
- impl Default for FallbackProvider {
- fn default() -> Self {
- Self::new()
- }
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
+ #[serde(rename_all = "kebab-case")]
+ pub struct CommandProvider {
+ yank: Command,
+ paste: Command,
+ yank_primary: Option,
+ paste_primary: Option,
}
- impl ClipboardProvider for FallbackProvider {
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
+ #[serde(rename_all = "kebab-case")]
+ #[allow(clippy::large_enum_variant)]
+ pub enum ClipboardProvider {
+ Pasteboard,
+ Wayland,
+ XClip,
+ XSel,
+ Win32Yank,
+ Tmux,
+ #[cfg(windows)]
+ Windows,
+ Termux,
#[cfg(feature = "term")]
- fn name(&self) -> Cow {
- Cow::Borrowed("termcode")
- }
-
- #[cfg(not(feature = "term"))]
- fn name(&self) -> Cow {
- Cow::Borrowed("none")
- }
+ Termcode,
+ Custom(CommandProvider),
+ None,
+ }
- fn get_contents(&self, clipboard_type: ClipboardType) -> Result {
- // This is the same noop if term is enabled or not.
- // We don't use the get side of OSC 52 as it isn't often enabled, it's a security hole,
- // and it would require this to be async to listen for the response
- let value = match clipboard_type {
- ClipboardType::Clipboard => self.buf.clone(),
- ClipboardType::Selection => self.primary_buf.clone(),
- };
+ impl Default for ClipboardProvider {
+ #[cfg(windows)]
+ fn default() -> Self {
+ use helix_stdx::env::binary_exists;
- Ok(value)
+ if binary_exists("win32yank.exe") {
+ Self::Win32Yank
+ } else {
+ Self::Windows
+ }
}
- fn set_contents(&mut self, content: String, clipboard_type: ClipboardType) -> Result<()> {
- #[cfg(feature = "term")]
- crossterm::execute!(
- std::io::stdout(),
- osc52::SetClipboardCommand::new(&content, clipboard_type)
- )?;
- // Set our internal variables to use in get_content regardless of using OSC 52
- match clipboard_type {
- ClipboardType::Clipboard => self.buf = content,
- ClipboardType::Selection => self.primary_buf = content,
+ #[cfg(target_os = "macos")]
+ fn default() -> Self {
+ use helix_stdx::env::{binary_exists, env_var_is_set};
+
+ if env_var_is_set("TMUX") && binary_exists("tmux") {
+ Self::Tmux
+ } else if binary_exists("pbcopy") && binary_exists("pbpaste") {
+ Self::Pasteboard
+ } else if cfg!(feature = "term") {
+ Self::Termcode
+ } else {
+ Self::None
}
- Ok(())
}
- }
-
- #[cfg(not(target_arch = "wasm32"))]
- pub mod command {
- use super::*;
- use anyhow::{bail, Context as _};
#[cfg(not(any(windows, target_os = "macos")))]
- pub fn is_exit_success(program: &str, args: &[&str]) -> bool {
- std::process::Command::new(program)
- .args(args)
- .output()
- .ok()
- .and_then(|out| out.status.success().then_some(()))
- .is_some()
- }
+ fn default() -> Self {
+ use helix_stdx::env::{binary_exists, env_var_is_set};
+
+ fn is_exit_success(program: &str, args: &[&str]) -> bool {
+ std::process::Command::new(program)
+ .args(args)
+ .output()
+ .ok()
+ .and_then(|out| out.status.success().then_some(()))
+ .is_some()
+ }
- #[derive(Debug)]
- pub struct Config {
- pub prg: &'static str,
- pub args: &'static [&'static str],
+ if env_var_is_set("WAYLAND_DISPLAY")
+ && binary_exists("wl-copy")
+ && binary_exists("wl-paste")
+ {
+ Self::Wayland
+ } else if env_var_is_set("DISPLAY") && binary_exists("xclip") {
+ Self::XClip
+ } else if env_var_is_set("DISPLAY")
+ && binary_exists("xsel")
+ // FIXME: check performance of is_exit_success
+ && is_exit_success("xsel", &["-o", "-b"])
+ {
+ Self::XSel
+ } else if binary_exists("termux-clipboard-set") && binary_exists("termux-clipboard-get")
+ {
+ Self::Termux
+ } else if env_var_is_set("TMUX") && binary_exists("tmux") {
+ Self::Tmux
+ } else if binary_exists("win32yank.exe") {
+ Self::Win32Yank
+ } else if cfg!(feature = "term") {
+ Self::Termcode
+ } else {
+ Self::None
+ }
}
+ }
- impl Config {
- fn execute(&self, input: Option<&str>, pipe_output: bool) -> Result