Merge pull request #1 from graelo/refinements

Misc refinements
This commit is contained in:
graelo 2022-11-07 00:15:58 +01:00 committed by GitHub
commit afa36d0c8f
29 changed files with 972 additions and 1116 deletions

View file

@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
rust: [1.56.0, stable, beta, nightly]
rust: [1.60.0, stable, beta, nightly]
steps:
- name: Rust install
uses: actions-rs/toolchain@v1
@ -61,7 +61,7 @@ jobs:
- name: Rust install
uses: actions-rs/toolchain@v1
with:
toolchain: 1.56.0
toolchain: 1.60.0
profile: minimal
override: true
components: rustfmt

View file

@ -3,8 +3,6 @@ on:
push:
branches:
- main
schedule:
- cron: '0 0 * * 0' # 00:00 Sunday
jobs:
@ -13,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
rust: [1.56.0, stable]
rust: [1.60.0, stable]
steps:
- name: Rust install
uses: actions-rs/toolchain@v1

View file

@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
rust: [1.56.0, stable]
rust: [1.60.0, stable]
steps:
- name: Rust install
uses: actions-rs/toolchain@v1
@ -33,7 +33,7 @@ jobs:
- name: Rust install
uses: actions-rs/toolchain@v1
with:
toolchain: 1.56.0
toolchain: 1.60.0
profile: minimal
override: true
components: rustfmt

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
/target
**/*.rs.bk
Cargo.lock

241
CONFIGURATION.md Normal file
View file

@ -0,0 +1,241 @@
# Configuration
If you want to customize how is shown your tmux-copyrat hints those all available
parameters to set your perfect profile.
NOTE: for changes to take effect, you'll need to source again your `.tmux.conf` file.
- [@copyrat-key](#thumbs-key)
- [@copyrat-alphabet](#thumbs-alphabet)
- [@copyrat-reverse](#thumbs-reverse)
- [@copyrat-unique](#thumbs-unique)
- [@copyrat-position](#thumbs-position)
- [@copyrat-regexp-N](#thumbs-regexp-N)
- [@copyrat-command](#thumbs-command)
- [@copyrat-upcase-command](#thumbs-upcase-command)
- [@copyrat-bg-color](#thumbs-bg-color)
- [@copyrat-fg-color](#thumbs-fg-color)
- [@copyrat-hint-bg-color](#thumbs-hint-bg-color)
- [@copyrat-hint-fg-color](#thumbs-hint-fg-color)
- [@copyrat-select-fg-color](#thumbs-select-fg-color)
- [@copyrat-select-bg-color](#thumbs-select-bg-color)
- [@copyrat-contrast](#thumbs-contrast)
### @thumbs-key
`default: space`
Choose which key is used to enter in thumbs mode.
For example:
```
set -g @thumbs-key F
```
### @thumbs-alphabet
`default: qwerty`
Choose which set of characters is used to build hints. Review all [available alphabets](#Alphabets)
For example:
```
set -g @thumbs-alphabet dvorak-homerow
```
### @thumbs-reverse
`default: disabled`
Choose in which direction you want to assign hints. Useful to get shorter hints closer to the cursor.
For example:
```
set -g @thumbs-reverse
```
### @thumbs-unique
`default: disabled`
Choose if you want to assign the same hint for the same text spans.
For example:
```
set -g @thumbs-unique
```
### @thumbs-position
`default: left`
Choose where do you want to show the hint in the text spans. Options (left, right).
For example:
```
set -g @thumbs-position right
```
### @thumbs-regexp-N
Add extra patterns to match. This parameter can have multiple instances.
For example:
```
set -g @thumbs-regexp-1 '[a-z]+@[a-z]+.com' # Match emails
set -g @thumbs-regexp-2 '[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:' # Match MAC addresses
```
### @thumbs-command
`default: 'tmux set-buffer {}'`
Choose which command execute when you press a hint. `tmux-thumbs` will replace `{}` with the picked hint.
For example:
```
set -g @thumbs-command 'echo -n {} | pbcopy'
```
### @thumbs-upcase-command
`default: 'tmux set-buffer {} && tmux paste-buffer'`
Choose which command execute when you press a upcase hint. `tmux-thumbs` will replace `{}` with the picked hint.
For example:
```
set -g @thumbs-upcase-command 'echo -n {} | pbcopy'
```
### @thumbs-bg-color
`default: black`
Sets the background color for spans
For example:
```
set -g @thumbs-bg-color blue
```
### @thumbs-fg-color
`default: green`
Sets the foreground color for spans
For example:
```
set -g @thumbs-fg-color green
```
### @thumbs-hint-bg-color
`default: black`
Sets the background color for hints
For example:
```
set -g @thumbs-hint-bg-color blue
```
### @thumbs-hint-fg-color
`default: yellow`
Sets the foreground color for hints
For example:
```
set -g @thumbs-hint-fg-color green
```
### @thumbs-select-fg-color
`default: blue`
Sets the foreground color for selection
For example:
```
set -g @thumbs-select-fg-color red
```
### @thumbs-select-bg-color
`default: black`
Sets the background color for selection
For example:
```
set -g @thumbs-select-bg-color red
```
### @thumbs-contrast
`default: 0`
Displays hint character in square brackets for extra visibility.
For example:
```
set -g @thumbs-contrast 1
```
#### Colors
This is the list of available colors:
- black
- red
- green
- yellow
- blue
- magenta
- cyan
- white
- default
#### Alphabets
This is the list of available alphabets:
- `qwerty`: asdfqwerzxcvjklmiuopghtybn
- `qwerty-homerow`: asdfjklgh
- `qwerty-left-hand`: asdfqwerzcxv
- `qwerty-right-hand`: jkluiopmyhn
- `azerty`: qsdfazerwxcvjklmuiopghtybn
- `azerty-homerow`: qsdfjkmgh
- `azerty-left-hand`: qsdfazerwxcv
- `azerty-right-hand`: jklmuiophyn
- `qwertz`: asdfqweryxcvjkluiopmghtzbn
- `qwertz-homerow`: asdfghjkl
- `qwertz-left-hand`: asdfqweryxcv
- `qwertz-right-hand`: jkluiopmhzn
- `dvorak`: aoeuqjkxpyhtnsgcrlmwvzfidb
- `dvorak-homerow`: aoeuhtnsid
- `dvorak-left-hand`: aoeupqjkyix
- `dvorak-right-hand`: htnsgcrlmwvz
- `colemak`: arstqwfpzxcvneioluymdhgjbk
- `colemak-homerow`: arstneiodh
- `colemak-left-hand`: arstqwfpzxcv
- `colemak-right-hand`: neioluymjhk

381
Cargo.lock generated
View file

@ -1,381 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "aho-corasick"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
dependencies = [
"memchr",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "clap"
version = "3.0.0-beta.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bd1061998a501ee7d4b6d449020df3266ca3124b941ec56cf2005c3779ca142"
dependencies = [
"atty",
"bitflags",
"clap_derive",
"indexmap",
"lazy_static",
"os_str_bytes",
"strsim",
"termcolor",
"terminal_size",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "clap_derive"
version = "3.0.0-beta.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "370f715b81112975b1b69db93e0b56ea4cd4e5002ac43b2da8474106a54096a1"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "copyrat"
version = "0.4.1"
dependencies = [
"clap",
"duct",
"regex",
"sequence_trie",
"termion",
]
[[package]]
name = "duct"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fc6a0a59ed0888e0041cf708e66357b7ae1a82f1c67247e1f93b5e0818f7d8d"
dependencies = [
"libc",
"once_cell",
"os_pipe",
"shared_child",
]
[[package]]
name = "hashbrown"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
[[package]]
name = "heck"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "hermit-abi"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
dependencies = [
"libc",
]
[[package]]
name = "indexmap"
version = "1.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8916b1f6ca17130ec6568feccee27c156ad12037880833a3b842a823236502e7"
[[package]]
name = "memchr"
version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
[[package]]
name = "numtoa"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
[[package]]
name = "once_cell"
version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
[[package]]
name = "os_pipe"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb233f06c2307e1f5ce2ecad9f8121cffbbee2c95428f44ea85222e460d0d213"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "os_str_bytes"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_termios"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
dependencies = [
"redox_syscall",
]
[[package]]
name = "regex"
version = "1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548"
[[package]]
name = "sequence_trie"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ee22067b7ccd072eeb64454b9c6e1b33b61cd0d49e895fd48676a184580e0c3"
[[package]]
name = "shared_child"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6be9f7d5565b1483af3e72975e2dee33879b3b86bd48c0929fccf6585d79e65a"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3a1d708c221c5a612956ef9f75b37e454e88d1f7b899fbd3a18d4252012d663"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "termcolor"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
dependencies = [
"winapi-util",
]
[[package]]
name = "terminal_size"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86ca8ced750734db02076f44132d802af0b33b09942331f4459dde8636fd2406"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "termion"
version = "1.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e"
dependencies = [
"libc",
"numtoa",
"redox_syscall",
"redox_termios",
]
[[package]]
name = "textwrap"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789"
dependencies = [
"terminal_size",
"unicode-width",
]
[[package]]
name = "unicode-segmentation"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
[[package]]
name = "unicode-width"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View file

@ -1,17 +1,26 @@
[package]
name = "copyrat"
version = "0.4.1"
authors = ["graelo <graelo@graelo.cc>"]
edition = "2018"
description = "This is tmux-copycat on Rust steroids."
repository = "https://github.com/graelo/tmux-copyrat"
keywords = ["rust", "tmux", "tmux-plugin", "tmux-copycat"]
edition = "2021"
description = "A tmux plugin for copy-pasting within tmux panes."
readme = "README.md"
license = "MIT"
authors = ["graelo <graelo@graelo.cc>"]
repository = "https://github.com/graelo/tmux-copyrat"
homepage = "https://github.com/graelo/tmux-copyrat"
documentation = "https://docs.rs/tmux-copyrat"
keywords = ["rust", "tmux", "tmux-plugin", "tmux-copycat"]
categories = ["command-line-utilities"]
exclude = ["/.github"]
[dependencies]
termion = "1.5"
regex = "1.4"
clap = { version = "3.0.0-beta.2", features = ["suggestions", "color", "wrap_help"]}
thiserror = "1"
termion = "2"
regex = "1.6"
clap = { version = "4.0", features = ["derive", "wrap_help"]}
sequence_trie = "0.3.6"
duct = "0.13"
@ -22,3 +31,26 @@ path = "src/bin/copyrat.rs"
[[bin]]
name = "tmux-copyrat"
path = "src/bin/tmux_copyrat.rs"
[profile.release]
# Enable link-time optimization (LTO). Its a kind of whole-program or
# inter-module optimization as it runs as the very last step when linking the
# different parts of your binary together. You can think of it as allowing
# better inlining across dependency boundaries (but its of course more
# complicated that that).
#
# Rust can use multiple linker flavors, and the one we want is “optimize across
# all crates”, which is called “fat”. To set this, add the lto flag to your
# profile:
lto = "fat"
# To speed up compile times, Rust tries to split your crates into small chunks
# and compile as many in parallel as possible. The downside is that theres
# less opportunities for the compiler to optimize code across these chunks. So,
# lets tell it to do one chunk per crate:
codegen-units = 1
# Rust by default uses stack unwinding (on the most common platforms). That
# costs performance, so lets skip stack traces and the ability to catch panics
# for reduced code size and better cache usage:
panic = "abort"

100
INSTALLATION.md Normal file
View file

@ -0,0 +1,100 @@
# Installation
## A note on extending tmux functionality
Extending [tmux] functionality is easy: it boils down to adding key-bindings
which call internal commands or external programs that you provide.
The minimalistic way to add functionality is simply to add your key-bindings
directly inside `~/.tmux.conf`. I don't do this because it ends up being messy,
but it's still an option described below.
In contrast, the most flexible way to extend is via plugins. You declare your
bindings in a plugin file often located in
`~/.tmux/plugins/your-plugin-dir/plugin-file.tmux`, and optionally provide your
external programs in the same folder or elsewhere. You then simply ask tmux to
_run_ your plugin file by adding `run-shell
~/.tmux/plugins/your-plugin-dir/plugin-file.tmux` inside your `tmux.conf`. Your
key-bindings will be registered on tmux initial start.
[TPM], the tmux plugin manager, is an integrated way of doing the same. It adds
a level of indirection: when tmux first starts, it runs TPM, which asks tmux to
_run_ all the plugin files in `~/.tmux/plugins/**/` as executables.
When run, each plugin file registers their key-bindings with tmux. TPM also has
an installation mechanism for plugins.
## Minimalistic installation
As described above, a valid option is to ignore the [`copyrat.tmux`] plugin
file simply add a few key bindings to tmux. You just have to create
key-bindings which launch the `tmux-copyrat` binary with its command line
options. Notice you probably need the absolute path to the binary.
However, when creating your bindings, avoid using `run-shell` to run `tmux-copyrat`
because by design tmux launches processes without attaching them to a pty.
Take inspiration from [`copyrat.tmux`] for correct syntax.
## Standard installation (recommended)
The easiest way to install is to copy the config file [`copyrat.tmux`] into `~/.tmux/plugins/tmux-copyrat/` and tell tmux to source it either via
- sourcing it directly from your `~/.tmux.conf`: you simply add the line `source-file ~/.tmux/plugins/tmux-copyrat/copyrat.tmux`
- or, if you use [TPM], registering it with TPM in your `~/.tmux.conf`: you simply add the line
```tmux
set -g @tpm_plugins ' \
tmux-plugins/tpm \
tmux-plugins/tmux-copyrat \ <- line added
tmux-plugins/tmux-yank \
...
```
second style of tmux integration is more declarative: you configure the tmux key bindings to pass none or very few command line arguments to `tmux-copyrat`, and ask `tmux-copyrat` to query back tmux for the rest of the configuration.
## Tmux integration
Clone the repo:
```
git clone https://github.com/graelo/tmux-copyrat ~/.tmux/plugins/tmux-copyrat
```
Compile it with [cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html):
```
cd ~/.tmux/plugins/tmux-copyrat
cargo build --release
```
Source it in your `.tmux.conf`:
```
run-shell ~/.tmux/plugins/tmux-copyrat/copyrat.tmux
```
Reload TMUX conf by running:
```
tmux source-file ~/.tmux.conf
```
## Using Tmux Plugin Manager
You can add this line to your list of [TPM](https://github.com/tmux-plugins/tpm) plugins in `.tmux.conf`:
```
set -g @plugin 'graelo/tmux-copyrat'
```
To be able to install the plugin just hit <kbd>prefix</kbd> + <kbd>I</kbd>. You should now be able to use
the plugin!
[`copyrat.tmux`]: https://raw.githubusercontent.com/graelo/tmux-copyrat/main/copyrat.tmux
[tmux]: https://tmux.github.io
[TPM]: https://github.com/tmux-plugins/tpm

View file

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2019 Ferran Basora
Copyright (c) 2022 graelo@grael.cc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

2
Makefile Normal file
View file

@ -0,0 +1,2 @@
release:
RUSTFLAGS="-Ctarget-cpu=native" cargo build --release

498
README.md
View file

@ -2,345 +2,144 @@
[![crate](https://img.shields.io/crates/v/tmux-copyrat.svg)](https://crates.io/crates/tmux-copyrat)
[![documentation](https://docs.rs/tmux-copyrat/badge.svg)](https://docs.rs/tmux-copyrat)
[![minimum rustc 1.8](https://img.shields.io/badge/rustc-1.56+-red.svg)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html)
[![minimum rustc 1.8](https://img.shields.io/badge/edition-2021-blue.svg)](https://doc.rust-lang.org/edition-guide/rust-2021/index.html)
[![minimum rustc 1.8](https://img.shields.io/badge/rustc-1.60+-blue.svg)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html)
[![edition 2021](https://img.shields.io/badge/edition-2021-blue.svg)](https://doc.rust-lang.org/edition-guide/rust-2021/index.html)
[![tmux 3.x](https://img.shields.io/badge/tmux-3.0+-blue.svg)](https://tmux.github.io)
[![build status](https://github.com/graelo/tmux-copyrat/workflows/main/badge.svg)](https://github.com/graelo/tmux-copyrat/actions)
A tmux-plugin for copy-pasting spans of text from the [tmux] pane's history
into a clipboard.
A hommage to [tmux-copyrat](https://github.com/tmux-plugins/tmux-copycat), written in [Rust](https://www.rust-lang.org/) for copy pasting within [tmux](http://tmux.github.io).
**Use case**: you're in tmux and press the key binding to highlight, say dates.
This makes `tmux-copyrat` search within tmux's current pane history and
highlight all spans of text which correspond to a date. All spans are displayed
with a one or two key _hint_, which you can then press to copy-paste the span
into the tmux clipboard or the system clipboard. Check out the demo below.
## Usage
The name is a tribute to [tmux-copyrat], which I used for many years for that
same functionality. For this Rust implementation, I got inspired by
[tmux-thumbs], and I even borrowed some parts of his regex tests.
Press ( <kbd>prefix</kbd> + <kbd>Space</kbd> ) to highlist in you current tmux
visible pane all text that match specific pattern. Then press the highlighted
letter hint to yank the text in your tmux buffer.
### Matched patterns
- File paths
- File in diff
- Git SHAs
- IPFS CID's
- Colors in hex
- Numbers ( 4+ digits )
- Hex numbers
- Markdown urls
- IP4 addresses
- Docker images
- kubernetes resources
- UUIDs
These are the list of matched patterns that will be highlighted by default. If
you want to highlight a pattern that is not in this list you can add one or
more with `--regexp` parameter.
## Demo
[![demo](https://asciinema.org/a/232775.png?ts=1)](https://asciinema.org/a/232775?autoplay=1)
## Tmux integration
Clone the repo:
## Usage
First install and optionally customize the plugin (see both [INSTALLATION.md]
and [CONFIGURATION.md] pages) and restart tmux.
Press one of the pre-defined tmux key-bindings (see table below) in order to
highlight spans of text matching a specific pattern. To yank some text span in
the tmux buffer, press the corresponding _hint_, or press <kbd>Esc</kbd> to
cancel and exit.
If instead you want to yank the text span into the system clipboard, either
press the caps version of the key hint (for instance <kbd>E</kbd> instead of
<kbd>e</kbd>), or first toggle the destination buffer with the <kbd>space</kbd>
key and press the hint with no caps.
You can also use the <kbd>n</kbd> and <kbd>N</kbd> (or <kbd>Up</kbd> and
<kbd>Down</kbd>) keys to move focus across the highlighted spans. Press
<kbd>y</kbd> to yank the focused span into the tmux buffer, or press
<kbd>Y</kbd> to yank it into the system clipboard.
By default, span highlighting starts from the bottom of the terminal, but you
can reverse that behavior with the `--reverse` option. The
`--focus-wrap-around` option makes navigation go back to the first span. Many
more options are described in [CONFIGURATION.md].
### Matched patterns and default key-bindings
tmux-copyrat can match one or more pre-defined (named) patterns, but you can
add your own too (see [CONFIGURATION.md]).
The default configuration provided in the [`copyrat.tmux`](copyrat.tmux) plugin
file provides the following key-bindings. Because they all start with
<kbd>prefix</kbd> + <kbd>t</kbd>, the table below only lists the keyboard key
that comes after. For instance, for URLs, the key is <kbd>u</kbd>, but you
should type <kbd>prefix</kbd> + <kbd>t</kbd> + <kbd>u</kbd>.
| key binding | searches for | pattern name |
| --- | --- | --- |
| <kbd>c</kbd> | Hex color codes | `hexcolor` |
| <kbd>d</kbd> | Dates or datetimes | `datetime` |
| <kbd>D</kbd> | Docker/Podman IDs | `docker` |
| <kbd>e</kbd> | Emails | `email` |
| <kbd>G</kbd> | String of 4+ digits | `digits` |
| <kbd>h</kbd> | SHA-1/-2 short & long | `sha` |
| <kbd>m</kbd> | Markdown URLs `[..](matched-url)` | `markdown-url` |
| <kbd>p</kbd> | Abs. and rel. filepaths | `path` |
| <kbd>P</kbd> | Hex numbers and pointer addresses | `pointer-address` |
| | strings inside single quotes | `quoted-single` |
| | strings inside double quotes | `quoted-double` |
| | strings inside backticks | `quoted-backtick` |
| <kbd>q</kbd> | strings inside single/double/backticks | |
| <kbd>u</kbd> | URLs | `url` |
| <kbd>U</kbd> | UUIDs | `uuid` |
| <kbd>v</kbd> | version numbers | `version` |
| <kbd>4</kbd> | IPv4 addresses | `4` |
| <kbd>6</kbd> | IPv6 addresses | `6` |
| <kbd>space</kbd> | All patterns | |
```
git clone https://github.com/graelo/tmux-copyrat ~/.tmux/plugins/tmux-copyrat
```
Compile it with [cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html):
```
cd ~/.tmux/plugins/tmux-copyrat
cargo build --release
```
Source it in your `.tmux.conf`:
```
run-shell ~/.tmux/plugins/tmux-copyrat/copyrat.tmux
```
Reload TMUX conf by running:
```
tmux source-file ~/.tmux.conf
```
## Using Tmux Plugin Manager
You can add this line to your list of [TPM](https://github.com/tmux-plugins/tpm) plugins in `.tmux.conf`:
```
set -g @plugin 'graelo/tmux-copyrat'
```
To be able to install the plugin just hit <kbd>prefix</kbd> + <kbd>I</kbd>. You should now be able to use
the plugin!
## Configuration
If you want to customize how is shown your tmux-copyrat hints those all available
parameters to set your perfect profile.
NOTE: for changes to take effect, you'll need to source again your `.tmux.conf` file.
- [@copyrat-key](#thumbs-key)
- [@copyrat-alphabet](#thumbs-alphabet)
- [@copyrat-reverse](#thumbs-reverse)
- [@copyrat-unique](#thumbs-unique)
- [@copyrat-position](#thumbs-position)
- [@copyrat-regexp-N](#thumbs-regexp-N)
- [@copyrat-command](#thumbs-command)
- [@copyrat-upcase-command](#thumbs-upcase-command)
- [@copyrat-bg-color](#thumbs-bg-color)
- [@copyrat-fg-color](#thumbs-fg-color)
- [@copyrat-hint-bg-color](#thumbs-hint-bg-color)
- [@copyrat-hint-fg-color](#thumbs-hint-fg-color)
- [@copyrat-select-fg-color](#thumbs-select-fg-color)
- [@copyrat-select-bg-color](#thumbs-select-bg-color)
- [@copyrat-contrast](#thumbs-contrast)
### @thumbs-key
`default: space`
Choose which key is used to enter in thumbs mode.
For example:
```
set -g @thumbs-key F
```
### @thumbs-alphabet
`default: qwerty`
Choose which set of characters is used to build hints. Review all [available alphabets](#Alphabets)
For example:
```
set -g @thumbs-alphabet dvorak-homerow
```
### @thumbs-reverse
`default: disabled`
Choose in which direction you want to assign hints. Useful to get shorter hints closer to the cursor.
For example:
```
set -g @thumbs-reverse
```
### @thumbs-unique
`default: disabled`
Choose if you want to assign the same hint for the same text spans.
For example:
```
set -g @thumbs-unique
```
### @thumbs-position
`default: left`
Choose where do you want to show the hint in the text spans. Options (left, right).
For example:
```
set -g @thumbs-position right
```
### @thumbs-regexp-N
Add extra patterns to match. This parameter can have multiple instances.
For example:
```
set -g @thumbs-regexp-1 '[a-z]+@[a-z]+.com' # Match emails
set -g @thumbs-regexp-2 '[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:' # Match MAC addresses
```
### @thumbs-command
`default: 'tmux set-buffer {}'`
Choose which command execute when you press a hint. `tmux-thumbs` will replace `{}` with the picked hint.
For example:
```
set -g @thumbs-command 'echo -n {} | pbcopy'
```
### @thumbs-upcase-command
`default: 'tmux set-buffer {} && tmux paste-buffer'`
Choose which command execute when you press a upcase hint. `tmux-thumbs` will replace `{}` with the picked hint.
For example:
```
set -g @thumbs-upcase-command 'echo -n {} | pbcopy'
```
### @thumbs-bg-color
`default: black`
Sets the background color for spans
For example:
```
set -g @thumbs-bg-color blue
```
### @thumbs-fg-color
`default: green`
Sets the foreground color for spans
For example:
```
set -g @thumbs-fg-color green
```
### @thumbs-hint-bg-color
`default: black`
Sets the background color for hints
For example:
```
set -g @thumbs-hint-bg-color blue
```
### @thumbs-hint-fg-color
`default: yellow`
Sets the foreground color for hints
For example:
```
set -g @thumbs-hint-fg-color green
```
### @thumbs-select-fg-color
`default: blue`
Sets the foreground color for selection
For example:
```
set -g @thumbs-select-fg-color red
```
### @thumbs-select-bg-color
`default: black`
Sets the background color for selection
For example:
```
set -g @thumbs-select-bg-color red
```
### @thumbs-contrast
`default: 0`
Displays hint character in square brackets for extra visibility.
For example:
```
set -g @thumbs-contrast 1
```
#### Colors
This is the list of available colors:
- black
- red
- green
- yellow
- blue
- magenta
- cyan
- white
- default
#### Alphabets
This is the list of available alphabets:
- `qwerty`: asdfqwerzxcvjklmiuopghtybn
- `qwerty-homerow`: asdfjklgh
- `qwerty-left-hand`: asdfqwerzcxv
- `qwerty-right-hand`: jkluiopmyhn
- `azerty`: qsdfazerwxcvjklmuiopghtybn
- `azerty-homerow`: qsdfjkmgh
- `azerty-left-hand`: qsdfazerwxcv
- `azerty-right-hand`: jklmuiophyn
- `qwertz`: asdfqweryxcvjkluiopmghtzbn
- `qwertz-homerow`: asdfghjkl
- `qwertz-left-hand`: asdfqweryxcv
- `qwertz-right-hand`: jkluiopmhzn
- `dvorak`: aoeuqjkxpyhtnsgcrlmwvzfidb
- `dvorak-homerow`: aoeuhtnsid
- `dvorak-left-hand`: aoeupqjkyix
- `dvorak-right-hand`: htnsgcrlmwvz
- `colemak`: arstqwfpzxcvneioluymdhgjbk
- `colemak-homerow`: arstneiodh
- `colemak-left-hand`: arstqwfpzxcv
- `colemak-right-hand`: neioluymjhk
## Extra features
- **Arrow navigation:** You can use the arrows to move around between all spans.
- **Auto paste:** If your last typed hint character is uppercase, you are going to pick and paste the desired hint.
- **Multi selection:** If you run thumb with multi selection mode you will be able to choose multiple hints pressing the desired letter and `Space` to finalize the selection.
## Tmux compatibility
This is the known list of versions of `tmux` compatible with `tmux-thumbs`:
`tmux-copyrat` is known to be compatible with tmux 3.0 onwards.
| Version | Compatible |
|:-------:|:----------:|
| 3.0b | ✅ |
| 2.9a | ✅ |
| 2.8 | ❓ |
| 2.7 | ❓ |
| 2.6 | ✅ |
| 2.5 | ❓ |
| 2.4 | ❓ |
| 2.3 | ❓ |
| 1.8 | ❓ |
| 1.7 | ❓ |
Testing this kind of integration with tmux is time consuming, so I'll be
grateful if you report incompatibilities as you find them.
## The `copyrat` standalone executable
Although the central binary of this crate is `tmux-copyrat`, the crate also
ships with the `copyrat` executable which provides the same functionality,
minus any tmux dependency or integration and instead reads from stdin.
You can use `copyrat` to search a span of text that you provide to stdin, à la
[FZF] but more focused and less interactive.
For instance here is a bunch of text, with dates and git hashes which you can
search with copyrat.
```text
* e006b06 - (12 days ago = 2021-03-04T12:23:34) e006b06 e006b06 swapper: Make quotes
/usr/local/bin/git
lorem
/usr/local/bin
lorem
The error was `Error no such file`
```
Let's imagine you want a quick way to always search for SHA-1/2, datetimes, strings within backticks, you would define once the following alias
```zsh
$ alias pick='copyrat -r --unique-hint -s bold -x sha -x datetime -x quoted-backtick | pbcopy'
```
and simply
```console
$ git log | pick
```
You will see the following in your terminal
![[copyrat-output.png](images/copyrat-output.png)](images/copyrat-output.png)
You may have noticed that all identical spans share the same _hint_, this is
due to the `-unique-hint` option (`-u`). The hints are in bold text, due to the
`--hint-style bold` option (`-s`). Hints start from the bottom, due to the
`--reverse` option (`-r`). A custom pattern was provided for matching any
"loca", due to the `--custom-regex-pattern` option (`-X`). The sha, datetime
and content inside backticks were highlighted due to the `--named-pattern`
option (`-x`).
If you can check hat `tmux-thumbs` is or is not compatible with some specific version of `tmux`, let me know.
## Standalone `thumbs`
@ -393,38 +192,51 @@ If you want to enjoy terminal hints, you can do things like this without `tmux`:
> git log | pick
```
Or multi selection:
```
> git log | thumbs -m
1df9fa69c8831ac042c6466af81e65402ee2a007
4897dc4ecbd2ac90b17de95e00e9e75bb540e37f
## Run code-coverage
Install the llvm-tools-preview component and grcov
```sh
rustup component add llvm-tools-preview
cargo install grcov
```
Standalone `thumbs` has some similarities to [FZF](https://github.com/junegunn/fzf).
Install nightly
## Background
```sh
rustup toolchain install nightly
```
As I said, this project is based in [tmux-fingers](https://github.com/Morantron/tmux-fingers). Morantron did an extraordinary job, building all necessary pieces in Bash to achieve the text picker behaviour. He only deserves my gratitude for all the time I have been using [tmux-fingers](https://github.com/Morantron/tmux-fingers).
The following make invocation will switch to nigthly run the tests using
Cargo, and output coverage HTML report in `./coverage/`
During a [Fosdem](https://fosdem.org/) conf, we had the idea to rewrite it to another language. He had these thoughts many times ago but it was hard to start from scratch. So, we decided to start playing with Node.js and [react-blessed](https://github.com/Yomguithereal/react-blessed), but we detected some unacceptable latency when the program booted. We didn't investigate much about this latency.
```sh
make coverage
```
During those days another alternative appeared, called [tmux-picker](https://github.com/RTBHOUSE/tmux-picker), implemented in python and reusing many parts from [tmux-fingers](https://github.com/Morantron/tmux-fingers). It was nice, because it was fast and added original terminal color support.
The coverage report is located in `./coverage/index.html`
I was curious to know if this was possible to be written in [Rust](https://www.rust-lang.org/), and soon I realized that was something doable. The ability to implement tests for all critic parts of the application give you a great confidence about it. On the other hand, Rust has an awesome community that lets you achieve this kind of project in a short period of time.
## Roadmap
- [X] Support multi selection
- [X] Decouple `tmux-thumbs` from `tmux`
- [ ] Code [Kitty](https://github.com/kovidgoyal/kitty) plugin, now that `thumbs` can run standalone
## License
## Contribute
This project is licensed under the [MIT license]
This project started as a side project to learn Rust, so I'm sure that is full
of mistakes and areas to be improve. If you think you can tweak the code to
make it better, I'll really appreaciate a pull request. ;)
at your option.
# License
[MIT](https://github.com/fcsonline/tmux-thumbs/blob/master/LICENSE)
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the MIT license, shall
be licensed as MIT, without any additional terms or conditions.
[tmux]: https://tmux.github.io
[tmux-copyrat]: https://github.com/tmux-plugins/tmux-copycat
[CONFIGURATION.md]: CONFIGURATION.md
[INSTALLATION.md]: INSTALLATION.md
[tmux-thumbs]: https://crates.io/crates/tmux-thumbs
[FZF]: https://github.com/junegunn/fzf
[xsel]: https://ostechnix.com/access-clipboard-contents-using-xclip-and-xsel-in-linux/
[MIT license]: http://opensource.org/licenses/MIT

View file

@ -5,6 +5,6 @@
set -ex
ci=$(dirname $0)
for version in 1.56.0 stable beta nightly; do
for version in 1.60.0 stable beta nightly; do
rustup run "$version" "$ci/test_full.sh"
done

View file

@ -3,7 +3,7 @@
set -e
CRATE=tmux-copyrat
MSRV=1.56
MSRV=1.60
get_rust_version() {
local array=($(rustc --version));

View file

@ -1,9 +1,10 @@
#!/usr/bin/env zsh
# This scripts provides a default configuration for tmux-copyrat options and key bindings.
# It is run only once at tmux launch.
# This scripts provides a default configuration for tmux-copyrat options and
# key bindings. It is run only once at tmux launch.
#
# Each option and binding can be overridden in your `tmux.conf` by defining options like
# Each option and binding can be overridden in your `tmux.conf` by defining
# options like
#
# set -g @copyrat-keytable "foobar"
# set -g @copyrat-keyswitch "z"
@ -14,9 +15,10 @@
# bind-key -T foobar h new-window -d -n "[copyrat]" '/path/to/tmux-copyrat --window-name "[copyrat]" --pattern-name urls'
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#
# changing this script may break integration with `tmux-copyrat`.
# Please avoid modifying this script as it may break the integration with
# `tmux-copyrat`.
#
#
# Just make sure you first open a named window in the background and provide
# that name to the binary `tmux-copyrat`.
#
@ -27,7 +29,7 @@
# options and bindings in your `tmux.conf`.
CURRENT_DIR="$( cd "$( dirname "$0" )" && pwd )"
BINARY="${CURRENT_DIR}/tmux-copyrat"
BINARY=${CURRENT_DIR}/tmux-copyrat
#
@ -50,8 +52,9 @@ setup_option "window-name" "[copyrat]"
# Get that window name as a local variable for use in pattern bindings below.
window_name=$(tmux show-option -gqv @copyrat-window-name)
# Sets the keytable for all bindings, providing a default if @copyrat-keytable was not defined.
# Keytables open a new shortcut space: if 't' is the switcher (see below), prefix + t + <your-shortcut>
# Sets the keytable for all bindings, providing a default if @copyrat-keytable
# was not defined. Keytables open a new shortcut space: if 't' is the switcher
# (see below), prefix + t + <your-shortcut>
setup_option "keytable" "cpyrt"
# Sets the key to access the keytable: prefix + <key> + <your-shortcut>
@ -94,7 +97,7 @@ setup_pattern_binding "p" "--pattern-name path"
# prefix + t + P searches for hex numbers: 0xbedead
setup_pattern_binding "P" "--pattern-name pointer-address"
# prefix + t + q searches for strings inside single|double|backticks
setup_pattern_binding "q" "-x quoted-single -x quoted-double -x quoted-tick"
setup_pattern_binding "q" "-x quoted-single -x quoted-double -x quoted-backtick"
# prefix + t + u searches for URLs
setup_pattern_binding "u" "--pattern-name url"
# prefix + t + U searches for UUIDs

BIN
images/copyrat-output.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

View file

@ -1,4 +1,4 @@
use clap::Clap;
use clap::Parser;
use std::io::{self, Read};
use copyrat::{config::basic, run, ui::Selection};

View file

@ -1,11 +1,11 @@
use copyrat::{
config::extended::{ConfigExt, OutputDestination},
error, tmux,
tmux,
ui::Selection,
Result,
};
///
fn main() -> Result<(), error::ParseError> {
fn main() -> Result<()> {
let config = ConfigExt::initialize()?;
// Identify active pane and capture its content.

View file

@ -1,14 +1,14 @@
use clap::Clap;
use std::str::FromStr;
use std::fmt::Display;
use clap::{ArgAction, Parser, ValueEnum};
use crate::{
error,
textbuf::{alphabet, regexes},
ui,
ui, Error, Result,
};
/// Main configuration, parsed from command line.
#[derive(Clap, Debug)]
#[derive(Parser, Debug)]
#[clap(author, about, version)]
pub struct Config {
/// Alphabet to draw hints from.
@ -20,57 +20,70 @@ pub struct Config {
/// # Examples
///
/// "qwerty", "dvorak-homerow", "azerty-right-hand".
#[clap(short = 'k', long, default_value = "dvorak",
parse(try_from_str = alphabet::parse_alphabet))]
#[arg(
short = 'k',
long,
default_value = "dvorak",
value_parser(alphabet::parse_alphabet)
)]
pub alphabet: alphabet::Alphabet,
/// Use all available regex patterns.
#[clap(short = 'A', long = "--all-patterns")]
#[arg(short = 'A', long = "all-patterns")]
pub use_all_patterns: bool,
/// Pattern names to use ("email", ... see doc).
#[clap(short = 'x', long = "--pattern-name", parse(try_from_str = regexes::parse_pattern_name))]
#[arg(
short = 'x',
long = "pattern-name",
value_parser(regexes::parse_pattern_name)
)]
pub named_patterns: Vec<regexes::NamedPattern>,
/// Additional regex patterns ("foo*bar", etc).
#[clap(short = 'X', long = "--custom-pattern")]
/// Additional regex patterns ("(foo.*)bar", etc). Must have a capture
/// group.
#[arg(short = 'X', long)]
pub custom_patterns: Vec<String>,
/// Assign hints starting from the bottom of the screen.
#[clap(short, long)]
#[arg(short, long, action = ArgAction::SetTrue)]
pub reverse: bool,
/// Keep the same hint for identical spans.
#[clap(short, long)]
#[arg(short, long, action = ArgAction::SetTrue)]
pub unique_hint: bool,
/// Move focus back to first/last span.
#[clap(short = 'w', long)]
#[arg(short = 'w', long, action = ArgAction::SetTrue)]
pub focus_wrap_around: bool,
#[clap(flatten)]
#[command(flatten)]
pub colors: ui::colors::UiColors,
/// Align hint with its span.
#[clap(long, arg_enum, default_value = "leading")]
#[arg(long, value_enum, default_value_t = ui::HintAlignment::Leading)]
pub hint_alignment: ui::HintAlignment,
/// Optional hint styling.
///
/// Underline or surround the hint for increased visibility.
/// If not provided, only the hint colors will be used.
#[clap(short = 's', long, arg_enum)]
pub hint_style: Option<HintStyleArg>,
#[arg(short = 's', long = "hint-style", rename_all = "lowercase", value_enum)]
pub hint_style_arg: Option<HintStyleArg>,
/// Chars surrounding each hint, used with `Surround` style.
#[clap(long, default_value = "{}",
parse(try_from_str = parse_chars))]
pub hint_surroundings: (char, char),
#[clap(
long,
// default_value_t = HintSurroundingsArg{open: '{', close: '}'},
default_value = "{}",
value_parser(try_parse_chars)
)]
pub hint_surroundings: HintSurroundingsArg,
}
/// Type introduced due to parsing limitation,
/// as we cannot directly parse into ui::HintStyle.
#[derive(Debug, Clap)]
/// as we cannot directly parse tuples into ui::HintStyle.
#[derive(Debug, Clone, ValueEnum)]
pub enum HintStyleArg {
Bold,
Italic,
@ -78,28 +91,44 @@ pub enum HintStyleArg {
Surround,
}
impl FromStr for HintStyleArg {
type Err = error::ParseError;
fn from_str(s: &str) -> Result<Self, error::ParseError> {
match s {
"bold" => Ok(HintStyleArg::Bold),
"italic" => Ok(HintStyleArg::Italic),
"underline" => Ok(HintStyleArg::Underline),
"surrond" => Ok(HintStyleArg::Surround),
_ => Err(error::ParseError::ExpectedString(String::from(
"bold, italic, underline or surround",
))),
#[derive(Debug, Clone)]
pub struct HintSurroundingsArg {
pub open: char,
pub close: char,
}
impl Display for HintSurroundingsArg {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}{}", self.open, self.close)
}
}
/// Try to parse a `&str` into a tuple of `char`s.
fn parse_chars(src: &str) -> Result<(char, char), error::ParseError> {
fn try_parse_chars(src: &str) -> Result<HintSurroundingsArg> {
if src.chars().count() != 2 {
return Err(error::ParseError::ExpectedSurroundingPair);
return Err(Error::ExpectedSurroundingPair);
}
let chars: Vec<char> = src.chars().collect();
Ok((chars[0], chars[1]))
Ok(HintSurroundingsArg {
open: chars[0],
close: chars[1],
})
}
impl Config {
pub fn hint_style(&self) -> Option<ui::HintStyle> {
match &self.hint_style_arg {
None => None,
Some(style) => match style {
HintStyleArg::Bold => Some(ui::HintStyle::Bold),
HintStyleArg::Italic => Some(ui::HintStyle::Italic),
HintStyleArg::Underline => Some(ui::HintStyle::Underline),
HintStyleArg::Surround => {
let HintSurroundingsArg { open, close } = self.hint_surroundings;
Some(ui::HintStyle::Surround(open, close))
}
},
}
}
}

View file

@ -1,15 +1,15 @@
use clap::Clap;
use std::collections::HashMap;
use std::fmt;
use std::str::FromStr;
use clap::{Parser, ValueEnum};
use super::basic;
use crate::{error, textbuf::alphabet, tmux, ui};
use crate::{textbuf::alphabet, tmux, ui, Error, Result};
/// Extended configuration for handling Tmux-specific configuration (options
/// and outputs). This is only used by `tmux-copyrat` and parsed from command
/// line..
#[derive(Clap, Debug)]
/// line.
#[derive(Parser, Debug)]
#[clap(author, about, version)]
pub struct ConfigExt {
/// Don't read options from Tmux.
@ -30,13 +30,19 @@ pub struct ConfigExt {
pub window_name: String,
/// Capture visible area or entire pane history.
#[clap(long, arg_enum, default_value = "visible-area")]
#[clap(
value_enum,
long,
rename_all = "kebab-case",
default_value = "visible-area"
)]
pub capture_region: CaptureRegion,
/// Name of the copy-to-clipboard executable.
///
/// If during execution, the output destination is set to be clipboard,
/// then copyrat will pipe the selected text to this executable.
/// On macOS, this is `pbcopy`, on Linux, this is `xclip`.
#[clap(long, default_value = "pbcopy")]
pub clipboard_exe: String,
@ -46,46 +52,54 @@ pub struct ConfigExt {
}
impl ConfigExt {
pub fn initialize() -> Result<ConfigExt, error::ParseError> {
pub fn initialize() -> Result<ConfigExt> {
let mut config_ext = ConfigExt::parse();
if !config_ext.ignore_tmux_options {
let tmux_options: HashMap<String, String> = tmux::get_options("@copyrat-")?;
// Override default values with those coming from tmux.
let wrapped = &mut config_ext.basic_config;
let inner = &mut config_ext.basic_config;
for (name, value) in &tmux_options {
match name.as_ref() {
"@copyrat-capture-region" => {
config_ext.capture_region = CaptureRegion::from_str(&value)?
let case_insensitive = true;
config_ext.capture_region = CaptureRegion::from_str(value, case_insensitive)
.map_err(Error::ExpectedEnumVariant)?
}
"@copyrat-alphabet" => {
wrapped.alphabet = alphabet::parse_alphabet(value)?;
inner.alphabet = alphabet::parse_alphabet(value)?;
}
"@copyrat-reverse" => {
wrapped.reverse = value.parse::<bool>()?;
inner.reverse = value.parse::<bool>()?;
}
"@copyrat-unique-hint" => {
wrapped.unique_hint = value.parse::<bool>()?;
inner.unique_hint = value.parse::<bool>()?;
}
"@copyrat-span-fg" => wrapped.colors.span_fg = ui::colors::parse_color(value)?,
"@copyrat-span-bg" => wrapped.colors.span_bg = ui::colors::parse_color(value)?,
"@copyrat-span-fg" => inner.colors.span_fg = ui::colors::parse_color(value)?,
"@copyrat-span-bg" => inner.colors.span_bg = ui::colors::parse_color(value)?,
"@copyrat-focused-fg" => {
wrapped.colors.focused_fg = ui::colors::parse_color(value)?
inner.colors.focused_fg = ui::colors::parse_color(value)?
}
"@copyrat-focused-bg" => {
wrapped.colors.focused_bg = ui::colors::parse_color(value)?
inner.colors.focused_bg = ui::colors::parse_color(value)?
}
"@copyrat-hint-fg" => wrapped.colors.hint_fg = ui::colors::parse_color(value)?,
"@copyrat-hint-bg" => wrapped.colors.hint_bg = ui::colors::parse_color(value)?,
"@copyrat-hint-fg" => inner.colors.hint_fg = ui::colors::parse_color(value)?,
"@copyrat-hint-bg" => inner.colors.hint_bg = ui::colors::parse_color(value)?,
"@copyrat-hint-alignment" => {
wrapped.hint_alignment = ui::HintAlignment::from_str(&value)?
let case_insensitive = true;
inner.hint_alignment = ui::HintAlignment::from_str(value, case_insensitive)
.map_err(Error::ExpectedEnumVariant)?
}
"@copyrat-hint-style" => {
wrapped.hint_style = Some(basic::HintStyleArg::from_str(&value)?)
let case_insensitive = true;
inner.hint_style_arg = Some(
basic::HintStyleArg::from_str(value, case_insensitive)
.map_err(Error::ExpectedEnumVariant)?,
)
}
// Ignore unknown options.
@ -98,11 +112,11 @@ impl ConfigExt {
}
}
#[derive(Clap, Debug)]
/// Specifies which region of the terminal buffer to capture.
#[derive(Debug, Clone, ValueEnum, Parser)]
pub enum CaptureRegion {
/// The entire history.
///
/// This will end up sending `-S - -E -` to `tmux capture-pane`.
// This will end up sending `-S - -E -` to `tmux capture-pane`.
EntireHistory,
/// The visible area.
VisibleArea,
@ -112,20 +126,6 @@ pub enum CaptureRegion {
//Region(i32, i32),
}
impl FromStr for CaptureRegion {
type Err = error::ParseError;
fn from_str(s: &str) -> Result<Self, error::ParseError> {
match s {
"entire-history" => Ok(CaptureRegion::EntireHistory),
"visible-area" => Ok(CaptureRegion::VisibleArea),
_ => Err(error::ParseError::ExpectedString(String::from(
"entire-history or visible-area",
))),
}
}
}
/// Describes the type of buffer the selected should be copied to: either a
/// tmux buffer or the system clipboard.
#[derive(Clone)]

View file

@ -1,50 +1,43 @@
use std::fmt;
// use std::fmt;
#[derive(Debug)]
pub enum ParseError {
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Expected 2 chars")]
ExpectedSurroundingPair,
#[error("Unknown alphabet")]
UnknownAlphabet,
#[error("Unknown ANSI color name: allowed values are magenta, cyan, black, ...")]
UnknownColor,
#[error("Unknown pattern name")]
UnknownPatternName,
#[error("Expected a pane id marker")]
ExpectedPaneIdMarker,
ExpectedInt(std::num::ParseIntError),
ExpectedBool(std::str::ParseBoolError),
#[error("Failed parsing integer")]
ExpectedInt {
#[from]
source: std::num::ParseIntError,
},
#[error("Failed to parse bool")]
ExpectedBool {
#[from]
source: std::str::ParseBoolError,
},
#[error("Expected the string `{0}`")]
ExpectedString(String),
ProcessFailure(String),
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ParseError::ExpectedSurroundingPair => write!(f, "Expected 2 chars"),
ParseError::UnknownAlphabet => write!(f, "Expected a known alphabet"),
ParseError::UnknownColor => {
write!(f, "Expected ANSI color name (magenta, cyan, black, ...)")
}
ParseError::UnknownPatternName => write!(f, "Expected a known pattern name"),
ParseError::ExpectedPaneIdMarker => write!(f, "Expected pane id marker"),
ParseError::ExpectedInt(msg) => write!(f, "Expected an int: {}", msg),
ParseError::ExpectedBool(msg) => write!(f, "Expected a bool: {}", msg),
ParseError::ExpectedString(msg) => write!(f, "Expected {}", msg),
ParseError::ProcessFailure(msg) => write!(f, "{}", msg),
}
}
}
#[error("Expected the value to be within `{0}`")]
ExpectedEnumVariant(String),
impl From<std::num::ParseIntError> for ParseError {
fn from(error: std::num::ParseIntError) -> Self {
ParseError::ExpectedInt(error)
}
}
impl From<std::str::ParseBoolError> for ParseError {
fn from(error: std::str::ParseBoolError) -> Self {
ParseError::ExpectedBool(error)
}
}
impl From<std::io::Error> for ParseError {
fn from(error: std::io::Error) -> Self {
ParseError::ProcessFailure(error.to_string())
}
#[error("IOError: `{source}`")]
Io {
#[from]
source: std::io::Error,
},
}

View file

@ -4,6 +4,9 @@ pub mod textbuf;
pub mod tmux;
pub mod ui;
pub use error::Error;
pub type Result<T> = std::result::Result<T, Error>;
/// Run copyrat on an input string `buffer`, configured by `Opt`.
///
/// # Note
@ -11,7 +14,7 @@ pub mod ui;
/// Maybe the decision to take ownership of the buffer is a bit bold.
pub fn run(lines: &[&str], opt: &config::basic::Config) -> Option<ui::Selection> {
let model = textbuf::Model::new(
&lines,
lines,
&opt.alphabet,
opt.use_all_patterns,
&opt.named_patterns,
@ -24,19 +27,6 @@ pub fn run(lines: &[&str], opt: &config::basic::Config) -> Option<ui::Selection>
return None;
}
let hint_style = match &opt.hint_style {
None => None,
Some(style) => match style {
config::basic::HintStyleArg::Bold => Some(ui::HintStyle::Bold),
config::basic::HintStyleArg::Italic => Some(ui::HintStyle::Italic),
config::basic::HintStyleArg::Underline => Some(ui::HintStyle::Underline),
config::basic::HintStyleArg::Surround => {
let (open, close) = opt.hint_surroundings;
Some(ui::HintStyle::Surround(open, close))
}
},
};
let default_output_destination = config::extended::OutputDestination::Tmux;
let selection: Option<ui::Selection> = {
@ -46,7 +36,7 @@ pub fn run(lines: &[&str], opt: &config::basic::Config) -> Option<ui::Selection>
default_output_destination,
&opt.colors,
&opt.hint_alignment,
hint_style,
opt.hint_style(),
);
ui.present()

View file

@ -1,4 +1,4 @@
use crate::error;
use crate::{Error, Result};
/// Catalog of available alphabets.
///
@ -41,7 +41,7 @@ const ALPHABETS: [(&str, &str); 21] = [
/// Letters 'n' and 'N' are systematically removed to prevent conflict with
/// navigation keys (arrows and 'n' 'N'). Letters 'y' and 'Y' are also removed
/// to prevent conflict with yank/copy.
pub fn parse_alphabet(src: &str) -> Result<Alphabet, error::ParseError> {
pub fn parse_alphabet(src: &str) -> Result<Alphabet> {
let alphabet_pair = ALPHABETS.iter().find(|&(name, _letters)| name == &src);
match alphabet_pair {
@ -49,12 +49,12 @@ pub fn parse_alphabet(src: &str) -> Result<Alphabet, error::ParseError> {
let letters = letters.replace(&['n', 'N', 'y', 'Y'][..], "");
Ok(Alphabet(letters))
}
None => Err(error::ParseError::UnknownAlphabet),
None => Err(Error::UnknownAlphabet),
}
}
/// Type-safe string alphabet (newtype).
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Alphabet(pub String);
impl Alphabet {

View file

@ -584,7 +584,7 @@ mod tests {
let named_pat = vec![
parse_pattern_name("quoted-single").unwrap(),
parse_pattern_name("quoted-double").unwrap(),
parse_pattern_name("quoted-tick").unwrap(),
parse_pattern_name("quoted-backtick").unwrap(),
];
let custom = vec![];

View file

@ -27,7 +27,7 @@ impl<'a> Model<'a> {
unique_hint: bool,
) -> Model<'a> {
let mut raw_spans =
find_raw_spans(&lines, named_patterns, custom_patterns, use_all_patterns);
find_raw_spans(lines, named_patterns, custom_patterns, use_all_patterns);
if reverse {
raw_spans.reverse();
@ -113,9 +113,10 @@ fn find_raw_spans<'a>(
// the start and end byte indices with respect to the chunk.
let chunk_matches = all_regexes
.iter()
.filter_map(|(&ref pat_name, reg)| match reg.find_iter(chunk).next() {
Some(reg_match) => Some((pat_name, reg, reg_match)),
None => None,
.filter_map(|(&ref pat_name, reg)| {
reg.find_iter(chunk)
.next()
.map(|reg_match| (pat_name, reg, reg_match))
})
.collect::<Vec<_>>();

View file

@ -2,7 +2,7 @@
//!
//! All patterns must have one capture group. The first group is used.
use crate::error;
use crate::{Error, Result};
pub(super) const EXCLUDE_PATTERNS: [(&str, &str); 1] =
[("ansi_colors", r"[[:cntrl:]]\[([0-9]{1,2};)?([0-9]{1,2})?m")];
@ -15,7 +15,7 @@ pub(super) const PATTERNS: [(&str, &str); 20] = [
("markdown-url", r"\[[^]]*\]\(([^)]+)\)"),
(
"url",
r"((https?://|git@|git://|ssh://|ftp://|file:///)[^ \(\)\[\]\{\}]+)",
r"((https?://|git@|git://|ssh://|ftp://|file:///)[^ \(\)\[\]\{\}>]+)",
),
("email", r"\b([A-z0-9._%+-]+@[A-z0-9.-]+\.[A-z]{2,})\b"),
("diff-a", r"--- a/([^ ]+)"),
@ -42,18 +42,18 @@ pub(super) const PATTERNS: [(&str, &str); 20] = [
),
("quoted-single", r#"'([^']+)'"#),
("quoted-double", r#""([^"]+)""#),
("quoted-tick", r#"`([^`]+)`"#),
("quoted-backtick", r#"`([^`]+)`"#),
("digits", r"([0-9]{4,})"),
];
/// Type-safe string Pattern Name (newtype).
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct NamedPattern(pub String, pub String);
/// Parse a name string into `NamedPattern`, used during CLI parsing.
pub(crate) fn parse_pattern_name(src: &str) -> Result<NamedPattern, error::ParseError> {
pub(crate) fn parse_pattern_name(src: &str) -> Result<NamedPattern> {
match PATTERNS.iter().find(|&(name, _pattern)| name == &src) {
Some((name, pattern)) => Ok(NamedPattern(name.to_string(), pattern.to_string())),
None => Err(error::ParseError::UnknownPatternName),
None => Err(Error::UnknownPatternName),
}
}

View file

@ -3,15 +3,18 @@
//! The main use cases are running Tmux commands & parsing Tmux panes
//! information.
use regex::Regex;
use std::collections::HashMap;
use std::fmt;
use std::str::FromStr;
use crate::config::extended::CaptureRegion;
use crate::error::ParseError;
use regex::Regex;
#[derive(Debug, PartialEq)]
use crate::config::extended::CaptureRegion;
use crate::{Error, Result};
/// Represents a simplified Tmux Pane, only holding the properties needed in
/// this crate.
#[derive(Debug, PartialEq, Eq)]
pub struct Pane {
/// Pane identifier, e.g. `%37`.
pub id: PaneId,
@ -30,7 +33,7 @@ pub struct Pane {
}
impl FromStr for Pane {
type Err = ParseError;
type Err = Error;
/// Parse a string containing tmux panes status into a new `Pane`.
///
@ -44,7 +47,7 @@ impl FromStr for Pane {
///
/// For definitions, look at `Pane` type,
/// and at the tmux man page for definitions.
fn from_str(src: &str) -> Result<Self, Self::Err> {
fn from_str(src: &str) -> std::result::Result<Self, Self::Err> {
let items: Vec<&str> = src.split(':').collect();
assert_eq!(items.len(), 5, "tmux should have returned 5 items per line");
@ -98,7 +101,7 @@ impl Pane {
/// scrolled up by 3 lines. It is necessarily in copy mode. Its start line
/// index is `-3`. The index of the last line is `(40-1) - 3 = 36`.
///
pub fn capture(&self, region: &CaptureRegion) -> Result<String, ParseError> {
pub fn capture(&self, region: &CaptureRegion) -> Result<String> {
let mut args_str = format!("capture-pane -t {pane_id} -J -p", pane_id = self.id);
let region_str = match region {
@ -125,17 +128,17 @@ impl Pane {
}
}
#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Eq)]
pub struct PaneId(String);
impl FromStr for PaneId {
type Err = ParseError;
type Err = Error;
/// Parse into PaneId. The `&str` must be start with '%'
/// followed by a `u16`.
fn from_str(src: &str) -> Result<Self, Self::Err> {
fn from_str(src: &str) -> std::result::Result<Self, Self::Err> {
if !src.starts_with('%') {
return Err(ParseError::ExpectedPaneIdMarker);
return Err(Error::ExpectedPaneIdMarker);
}
let id = src[1..].parse::<u16>()?;
let id = format!("%{}", id);
@ -156,7 +159,7 @@ impl fmt::Display for PaneId {
}
/// Returns a list of `Pane` from the current tmux session.
pub fn available_panes() -> Result<Vec<Pane>, ParseError> {
pub fn available_panes() -> Result<Vec<Pane>> {
let args = vec![
"list-panes",
"-F",
@ -165,12 +168,12 @@ pub fn available_panes() -> Result<Vec<Pane>, ParseError> {
let output = duct::cmd("tmux", &args).read()?;
// Each call to `Pane::parse` returns a `Result<Pane, _>`. All results
// are collected into a Result<Vec<Pane>, _>, thanks to `collect()`.
let result: Result<Vec<Pane>, ParseError> = output
// Each call to `Pane::parse` returns a `Result<Pane>`. All results
// are collected into a Result<Vec<Pane>>, thanks to `collect()`.
let result: Result<Vec<Pane>> = output
.trim_end() // trim last '\n' as it would create an empty line
.split('\n')
.map(|line| Pane::from_str(line))
.map(Pane::from_str) // .map(|line| Pane::from_str(line))
.collect();
result
@ -182,7 +185,7 @@ pub fn available_panes() -> Result<Vec<Pane>, ParseError> {
///
/// # Example
/// ```get_options("@copyrat-")```
pub fn get_options(prefix: &str) -> Result<HashMap<String, String>, ParseError> {
pub fn get_options(prefix: &str) -> Result<HashMap<String, String>> {
let output = duct::cmd!("tmux", "show-options", "-g").read()?;
let lines: Vec<&str> = output.split('\n').collect();
@ -204,8 +207,8 @@ pub fn get_options(prefix: &str) -> Result<HashMap<String, String>, ParseError>
Ok(args)
}
/// Ask tmux to swap the current Pane with the target_pane (uses Tmux format).
pub fn swap_pane_with(target_pane: &str) -> Result<(), ParseError> {
/// Asks tmux to swap the current Pane with the target_pane (uses Tmux format).
pub fn swap_pane_with(target_pane: &str) -> Result<()> {
// -Z: keep the window zoomed if it was zoomed.
duct::cmd!("tmux", "swap-pane", "-Z", "-s", target_pane).run()?;
@ -214,16 +217,13 @@ pub fn swap_pane_with(target_pane: &str) -> Result<(), ParseError> {
#[cfg(test)]
mod tests {
use super::Pane;
use super::PaneId;
use crate::error;
use super::*;
use std::str::FromStr;
#[test]
fn test_parse_pass() {
let output = vec!["%52:false:62:3:false", "%53:false:23::true"];
let panes: Result<Vec<Pane>, error::ParseError> =
output.iter().map(|&line| Pane::from_str(line)).collect();
let panes: Result<Vec<Pane>> = output.iter().map(|&line| Pane::from_str(line)).collect();
let panes = panes.expect("Could not parse tmux panes");
let expected = vec![

View file

@ -1,49 +1,107 @@
use crate::error;
use clap::Clap;
use termion::color;
use std::fmt;
use std::str::FromStr;
pub fn parse_color(src: &str) -> Result<Box<dyn color::Color>, error::ParseError> {
match src {
"black" => Ok(Box::new(color::Black)),
"red" => Ok(Box::new(color::Red)),
"green" => Ok(Box::new(color::Green)),
"yellow" => Ok(Box::new(color::Yellow)),
"blue" => Ok(Box::new(color::Blue)),
"magenta" => Ok(Box::new(color::Magenta)),
"cyan" => Ok(Box::new(color::Cyan)),
"white" => Ok(Box::new(color::White)),
"bright-black" | "brightblack" => Ok(Box::new(color::LightBlack)),
"bright-red" | "brightred" => Ok(Box::new(color::LightRed)),
"bright-green" | "brightgreen" => Ok(Box::new(color::LightGreen)),
"bright-yellow" | "brightyellow" => Ok(Box::new(color::LightYellow)),
"bright-blue" | "brightblue" => Ok(Box::new(color::LightBlue)),
"bright-magenta" | "brightmagenta" => Ok(Box::new(color::LightMagenta)),
"bright-cyan" | "brightcyan" => Ok(Box::new(color::LightCyan)),
"bright-white" | "brightwhite" => Ok(Box::new(color::LightWhite)),
"none" => Ok(Box::new(color::Reset)),
_ => Err(error::ParseError::UnknownColor),
use clap::Args;
use termion::color as tcolor;
use crate::{Error, Result};
#[derive(Debug, Clone, Copy)]
pub struct Color(Option<u8>);
impl tcolor::Color for Color {
#[inline]
fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0 {
Some(value) => write!(f, "\x1B[38;5;{}m", value),
None => write!(f, "\x1B[39m"),
}
}
#[inline]
fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0 {
Some(value) => write!(f, "\x1B[48;5;{}m", value),
None => write!(f, "\x1B[49m"),
}
}
}
pub(crate) static BLACK: Color = Color(Some(0));
pub(crate) static RED: Color = Color(Some(1));
pub(crate) static GREEN: Color = Color(Some(2));
pub(crate) static YELLOW: Color = Color(Some(3));
pub(crate) static BLUE: Color = Color(Some(4));
pub(crate) static MAGENTA: Color = Color(Some(5));
pub(crate) static CYAN: Color = Color(Some(6));
pub(crate) static WHITE: Color = Color(Some(7));
pub(crate) static BRIGHTBLACK: Color = Color(Some(8));
pub(crate) static BRIGHTRED: Color = Color(Some(9));
pub(crate) static BRIGHTGREEN: Color = Color(Some(10));
pub(crate) static BRIGHTYELLOW: Color = Color(Some(11));
pub(crate) static BRIGHTBLUE: Color = Color(Some(12));
pub(crate) static BRIGHTMAGENTA: Color = Color(Some(13));
pub(crate) static BRIGHTCYAN: Color = Color(Some(14));
pub(crate) static BRIGHTWHITE: Color = Color(Some(15));
pub(crate) static RESET: Color = Color(None);
impl FromStr for Color {
type Err = Error;
fn from_str(src: &str) -> std::result::Result<Self, Self::Err> {
match src {
"black" => Ok(BLACK),
"red" => Ok(RED),
"green" => Ok(GREEN),
"yellow" => Ok(YELLOW),
"blue" => Ok(BLUE),
"magenta" => Ok(MAGENTA),
"cyan" => Ok(CYAN),
"white" => Ok(WHITE),
"bright-black" | "brightblack" => Ok(BRIGHTBLACK),
"bright-red" | "brightred" => Ok(BRIGHTRED),
"bright-green" | "brightgreen" => Ok(BRIGHTGREEN),
"bright-yellow" | "brightyellow" => Ok(BRIGHTYELLOW),
"bright-blue" | "brightblue" => Ok(BRIGHTBLUE),
"bright-magenta" | "brightmagenta" => Ok(BRIGHTMAGENTA),
"bright-cyan" | "brightcyan" => Ok(BRIGHTCYAN),
"bright-white" | "brightwhite" => Ok(BRIGHTWHITE),
"none" => Ok(RESET),
_ => Err(Error::UnknownColor),
}
}
}
pub fn parse_color(src: &str) -> Result<Color> {
Color::from_str(src)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn span_color() {
let text1 = format!(
"{}{}",
color::Fg(parse_color("green").unwrap().as_ref()),
"foo"
);
let text2 = format!("{}{}", color::Fg(color::Green), "foo");
fn span_color_fg() {
let actual = format!("{}{}", tcolor::Fg(Color::from_str("green").unwrap()), "foo");
let expected = format!("{}{}", tcolor::Fg(tcolor::Green), "foo");
assert_eq!(text1, text2);
assert_eq!(actual, expected);
}
#[test]
fn span_color_bg() {
let actual = format!("{}{}", tcolor::Bg(Color::from_str("green").unwrap()), "foo");
let expected = format!("{}{}", tcolor::Bg(tcolor::Green), "foo");
assert_eq!(actual, expected);
}
#[test]
fn no_span_color() {
assert!(parse_color("wat").is_err(), "this color should not exist");
assert!(
Color::from_str("wat").is_err(),
"this color should not exist"
);
}
}
@ -52,44 +110,38 @@ mod tests {
/// - `focus_*` colors are used to render the currently focused text span.
/// - `normal_*` colors are used to render other text spans.
/// - `hint_*` colors are used to render the hints.
#[derive(Clap, Debug)]
#[clap(about)] // Needed to avoid this doc comment to be used as overall `about`.
#[derive(Args, Debug)]
// #[clap(about)] // Needed to avoid this doc comment to be used as overall `about`.
pub struct UiColors {
/// Foreground color for base text.
#[clap(long, default_value = "bright-cyan", parse(try_from_str = parse_color))]
pub text_fg: Box<dyn color::Color>,
#[arg(long, default_value = "bright-cyan", value_parser(parse_color))]
pub text_fg: Color,
/// Background color for base text.
#[clap(long, default_value = "none", parse(try_from_str = parse_color))]
pub text_bg: Box<dyn color::Color>,
#[clap(long, default_value = "none", value_parser(parse_color))]
pub text_bg: Color,
/// Foreground color for spans.
#[clap(long, default_value = "blue",
parse(try_from_str = parse_color))]
pub span_fg: Box<dyn color::Color>,
#[clap(long, default_value = "blue", value_parser(parse_color))]
pub span_fg: Color,
/// Background color for spans.
#[clap(long, default_value = "none",
parse(try_from_str = parse_color))]
pub span_bg: Box<dyn color::Color>,
#[clap(long, default_value = "none", value_parser(parse_color))]
pub span_bg: Color,
/// Foreground color for the focused span.
#[clap(long, default_value = "magenta",
parse(try_from_str = parse_color))]
pub focused_fg: Box<dyn color::Color>,
#[clap(long, default_value = "magenta", value_parser(parse_color))]
pub focused_fg: Color,
/// Background color for the focused span.
#[clap(long, default_value = "none",
parse(try_from_str = parse_color))]
pub focused_bg: Box<dyn color::Color>,
#[clap(long, default_value = "none", value_parser(parse_color))]
pub focused_bg: Color,
/// Foreground color for hints.
#[clap(long, default_value = "yellow",
parse(try_from_str = parse_color))]
pub hint_fg: Box<dyn color::Color>,
#[clap(long, default_value = "yellow", value_parser(parse_color))]
pub hint_fg: Color,
/// Background color for hints.
#[clap(long, default_value = "none",
parse(try_from_str = parse_color))]
pub hint_bg: Box<dyn color::Color>,
#[clap(long, default_value = "none", value_parser(parse_color))]
pub hint_bg: Color,
}

View file

@ -1,26 +1,9 @@
use clap::Clap;
use std::str::FromStr;
use crate::error::ParseError;
use clap::{Parser, ValueEnum};
/// Describes if, during rendering, a hint should aligned to the leading edge of
/// the matched text, or to its trailing edge.
#[derive(Debug, Clap)]
#[derive(Debug, Clone, ValueEnum, Parser)]
pub enum HintAlignment {
Leading,
Trailing,
}
impl FromStr for HintAlignment {
type Err = ParseError;
fn from_str(s: &str) -> Result<HintAlignment, ParseError> {
match s {
"leading" => Ok(HintAlignment::Leading),
"trailing" => Ok(HintAlignment::Trailing),
_ => Err(ParseError::ExpectedString(String::from(
"leading or trailing",
))),
}
}
}

View file

@ -1,8 +1,9 @@
use std::char;
use std::cmp;
use std::io;
use std::io::Write;
use termion::{self, color, cursor, event, style};
use termion::{self, color, cursor, event, screen::IntoAlternateScreen, style};
use super::colors::UiColors;
use super::Selection;
@ -63,7 +64,7 @@ impl<'a> ViewController<'a> {
};
let (term_width, _) = termion::terminal_size().unwrap_or((80u16, 30u16)); // .expect("Cannot read the terminal size.");
let wrapped_lines = compute_wrapped_lines(&model.lines, term_width);
let wrapped_lines = compute_wrapped_lines(model.lines, term_width);
ViewController {
model,
@ -177,8 +178,8 @@ impl<'a> ViewController<'a> {
write!(
stdout,
"{bg_color}{fg_color}",
fg_color = color::Fg(colors.text_fg.as_ref()),
bg_color = color::Bg(colors.text_bg.as_ref()),
fg_color = color::Fg(colors.text_fg),
bg_color = color::Bg(colors.text_bg),
)
.unwrap();
@ -233,8 +234,8 @@ impl<'a> ViewController<'a> {
stdout,
"{goto}{bg_color}{fg_color}{text}{fg_reset}{bg_reset}",
goto = cursor::Goto(pos.0 as u16 + 1, pos.1 as u16 + 1),
fg_color = color::Fg(fg_color.as_ref()),
bg_color = color::Bg(bg_color.as_ref()),
fg_color = color::Fg(*fg_color),
bg_color = color::Bg(*bg_color),
fg_reset = color::Fg(color::Reset),
bg_reset = color::Bg(color::Reset),
text = &text,
@ -260,8 +261,8 @@ impl<'a> ViewController<'a> {
colors: &UiColors,
hint_style: &Option<HintStyle>,
) {
let fg_color = color::Fg(colors.hint_fg.as_ref());
let bg_color = color::Bg(colors.hint_bg.as_ref());
let fg_color = color::Fg(colors.hint_fg);
let bg_color = color::Bg(colors.hint_bg);
let fg_reset = color::Fg(color::Reset);
let bg_reset = color::Bg(color::Reset);
let goto = cursor::Goto(pos.0 as u16 + 1, pos.1 as u16 + 1);
@ -358,7 +359,7 @@ impl<'a> ViewController<'a> {
text,
focused,
(pos_x, pos_y),
&self.rendering_colors,
self.rendering_colors,
);
if !focused {
@ -374,7 +375,7 @@ impl<'a> ViewController<'a> {
stdout,
&span.hint,
(pos_x + offset, pos_y),
&self.rendering_colors,
self.rendering_colors,
&self.hint_style,
);
}
@ -399,9 +400,9 @@ impl<'a> ViewController<'a> {
// 1. Trim all lines and render non-empty ones.
ViewController::render_base_text(
stdout,
&self.model.lines,
self.model.lines,
&self.wrapped_lines,
&self.rendering_colors,
self.rendering_colors,
);
for (index, span) in self.model.spans.iter().enumerate() {
@ -598,17 +599,16 @@ impl<'a> ViewController<'a> {
/// - Setup steps: switch to alternate screen, switch to raw mode, hide the cursor.
/// - Teardown steps: show cursor, back to main screen.
pub fn present(&mut self) -> Option<Selection> {
use std::io::Write;
use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen;
let mut stdin = termion::async_stdin();
let mut stdout = AlternateScreen::from(
io::stdout()
let mut stdout = io::stdout()
.into_alternate_screen()
.expect("Cannot access alternate screen.")
.into_raw_mode()
.expect("Cannot access alternate screen."),
);
.expect("Cannot access alternate screen.");
// stdout.write(cursor::Hide.into()).unwrap();
write!(stdout, "{}", cursor::Hide).unwrap();
let selection = match self.listen(&mut stdin, &mut stdout) {
@ -663,7 +663,7 @@ enum Event {
#[cfg(test)]
mod tests {
use super::*;
use crate::textbuf::alphabet;
use crate::{textbuf::alphabet, ui::colors};
#[test]
fn test_render_all_lines() {
@ -684,14 +684,14 @@ path: /usr/local/bin/cargo";
];
let colors = UiColors {
text_fg: Box::new(color::Black),
text_bg: Box::new(color::White),
focused_fg: Box::new(color::Red),
focused_bg: Box::new(color::Blue),
span_fg: Box::new(color::Green),
span_bg: Box::new(color::Magenta),
hint_fg: Box::new(color::Yellow),
hint_bg: Box::new(color::Cyan),
text_fg: colors::BLACK,
text_bg: colors::WHITE,
focused_fg: colors::RED,
focused_bg: colors::BLUE,
span_fg: colors::GREEN,
span_bg: colors::MAGENTA,
hint_fg: colors::YELLOW,
hint_bg: colors::CYAN,
};
let mut writer = vec![];
@ -706,8 +706,8 @@ path: /usr/local/bin/cargo";
format!(
"{bg}{fg}{g1}some text{g2}* e006b06 - (12 days ago) swapper: Make quotes{g3}path: /usr/local/bin/git{g6}path: /usr/local/bin/cargo{fg_reset}{bg_reset}",
g1 = goto1, g2 = goto2, g3 = goto3, g6 = goto6,
fg = color::Fg(colors.text_fg.as_ref()),
bg = color::Bg(colors.text_bg.as_ref()),
fg = color::Fg(colors.text_fg),
bg = color::Bg(colors.text_bg),
fg_reset = color::Fg(color::Reset),
bg_reset = color::Bg(color::Reset),
)
@ -722,14 +722,14 @@ path: /usr/local/bin/cargo";
let focused = true;
let position: (usize, usize) = (3, 1);
let colors = UiColors {
text_fg: Box::new(color::Black),
text_bg: Box::new(color::White),
focused_fg: Box::new(color::Red),
focused_bg: Box::new(color::Blue),
span_fg: Box::new(color::Green),
span_bg: Box::new(color::Magenta),
hint_fg: Box::new(color::Yellow),
hint_bg: Box::new(color::Cyan),
text_fg: colors::BLACK,
text_bg: colors::WHITE,
focused_fg: colors::RED,
focused_bg: colors::BLUE,
span_fg: colors::GREEN,
span_bg: colors::MAGENTA,
hint_fg: colors::YELLOW,
hint_bg: colors::CYAN,
};
ViewController::render_span_text(&mut writer, text, focused, position, &colors);
@ -739,8 +739,8 @@ path: /usr/local/bin/cargo";
format!(
"{goto}{bg}{fg}{text}{fg_reset}{bg_reset}",
goto = cursor::Goto(4, 2),
fg = color::Fg(colors.focused_fg.as_ref()),
bg = color::Bg(colors.focused_bg.as_ref()),
fg = color::Fg(colors.focused_fg),
bg = color::Bg(colors.focused_bg),
fg_reset = color::Fg(color::Reset),
bg_reset = color::Bg(color::Reset),
text = &text,
@ -756,14 +756,14 @@ path: /usr/local/bin/cargo";
let focused = false;
let position: (usize, usize) = (3, 1);
let colors = UiColors {
text_fg: Box::new(color::Black),
text_bg: Box::new(color::White),
focused_fg: Box::new(color::Red),
focused_bg: Box::new(color::Blue),
span_fg: Box::new(color::Green),
span_bg: Box::new(color::Magenta),
hint_fg: Box::new(color::Yellow),
hint_bg: Box::new(color::Cyan),
text_fg: colors::BLACK,
text_bg: colors::WHITE,
focused_fg: colors::RED,
focused_bg: colors::BLUE,
span_fg: colors::GREEN,
span_bg: colors::MAGENTA,
hint_fg: colors::YELLOW,
hint_bg: colors::CYAN,
};
ViewController::render_span_text(&mut writer, text, focused, position, &colors);
@ -773,8 +773,8 @@ path: /usr/local/bin/cargo";
format!(
"{goto}{bg}{fg}{text}{fg_reset}{bg_reset}",
goto = cursor::Goto(4, 2),
fg = color::Fg(colors.span_fg.as_ref()),
bg = color::Bg(colors.span_bg.as_ref()),
fg = color::Fg(colors.span_fg),
bg = color::Bg(colors.span_bg),
fg_reset = color::Fg(color::Reset),
bg_reset = color::Bg(color::Reset),
text = &text,
@ -789,14 +789,14 @@ path: /usr/local/bin/cargo";
let hint_text = "eo";
let position: (usize, usize) = (3, 1);
let colors = UiColors {
text_fg: Box::new(color::Black),
text_bg: Box::new(color::White),
focused_fg: Box::new(color::Red),
focused_bg: Box::new(color::Blue),
span_fg: Box::new(color::Green),
span_bg: Box::new(color::Magenta),
hint_fg: Box::new(color::Yellow),
hint_bg: Box::new(color::Cyan),
text_fg: colors::BLACK,
text_bg: colors::WHITE,
focused_fg: colors::RED,
focused_bg: colors::BLUE,
span_fg: colors::GREEN,
span_bg: colors::MAGENTA,
hint_fg: colors::YELLOW,
hint_bg: colors::CYAN,
};
let offset = 0;
@ -815,8 +815,8 @@ path: /usr/local/bin/cargo";
format!(
"{goto}{bg}{fg}{text}{fg_reset}{bg_reset}",
goto = cursor::Goto(4, 2),
fg = color::Fg(colors.hint_fg.as_ref()),
bg = color::Bg(colors.hint_bg.as_ref()),
fg = color::Fg(colors.hint_fg),
bg = color::Bg(colors.hint_bg),
fg_reset = color::Fg(color::Reset),
bg_reset = color::Bg(color::Reset),
text = "eo",
@ -831,14 +831,14 @@ path: /usr/local/bin/cargo";
let hint_text = "eo";
let position: (usize, usize) = (3, 1);
let colors = UiColors {
text_fg: Box::new(color::Black),
text_bg: Box::new(color::White),
focused_fg: Box::new(color::Red),
focused_bg: Box::new(color::Blue),
span_fg: Box::new(color::Green),
span_bg: Box::new(color::Magenta),
hint_fg: Box::new(color::Yellow),
hint_bg: Box::new(color::Cyan),
text_fg: colors::BLACK,
text_bg: colors::WHITE,
focused_fg: colors::RED,
focused_bg: colors::BLUE,
span_fg: colors::GREEN,
span_bg: colors::MAGENTA,
hint_fg: colors::YELLOW,
hint_bg: colors::CYAN,
};
let offset = 0;
@ -857,8 +857,8 @@ path: /usr/local/bin/cargo";
format!(
"{goto}{bg}{fg}{sty}{text}{sty_reset}{fg_reset}{bg_reset}",
goto = cursor::Goto(4, 2),
fg = color::Fg(colors.hint_fg.as_ref()),
bg = color::Bg(colors.hint_bg.as_ref()),
fg = color::Fg(colors.hint_fg),
bg = color::Bg(colors.hint_bg),
fg_reset = color::Fg(color::Reset),
bg_reset = color::Bg(color::Reset),
sty = style::Underline,
@ -875,14 +875,14 @@ path: /usr/local/bin/cargo";
let hint_text = "eo";
let position: (usize, usize) = (3, 1);
let colors = UiColors {
text_fg: Box::new(color::Black),
text_bg: Box::new(color::White),
focused_fg: Box::new(color::Red),
focused_bg: Box::new(color::Blue),
span_fg: Box::new(color::Green),
span_bg: Box::new(color::Magenta),
hint_fg: Box::new(color::Yellow),
hint_bg: Box::new(color::Cyan),
text_fg: colors::BLACK,
text_bg: colors::WHITE,
focused_fg: colors::RED,
focused_bg: colors::BLUE,
span_fg: colors::GREEN,
span_bg: colors::MAGENTA,
hint_fg: colors::YELLOW,
hint_bg: colors::CYAN,
};
let offset = 0;
@ -901,8 +901,8 @@ path: /usr/local/bin/cargo";
format!(
"{goto}{bg}{fg}{bra}{text}{bra_close}{fg_reset}{bg_reset}",
goto = cursor::Goto(4, 2),
fg = color::Fg(colors.hint_fg.as_ref()),
bg = color::Bg(colors.hint_bg.as_ref()),
fg = color::Fg(colors.hint_fg),
bg = color::Bg(colors.hint_bg),
fg_reset = color::Fg(color::Reset),
bg_reset = color::Bg(color::Reset),
bra = '{',
@ -937,16 +937,16 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
unique_hint,
);
let term_width: u16 = 80;
let wrapped_lines = compute_wrapped_lines(&model.lines, term_width);
let wrapped_lines = compute_wrapped_lines(model.lines, term_width);
let rendering_colors = UiColors {
text_fg: Box::new(color::Black),
text_bg: Box::new(color::White),
focused_fg: Box::new(color::Red),
focused_bg: Box::new(color::Blue),
span_fg: Box::new(color::Green),
span_bg: Box::new(color::Magenta),
hint_fg: Box::new(color::Yellow),
hint_bg: Box::new(color::Cyan),
text_fg: colors::BLACK,
text_bg: colors::WHITE,
focused_fg: colors::RED,
focused_bg: colors::BLUE,
span_fg: colors::GREEN,
span_bg: colors::MAGENTA,
hint_fg: colors::YELLOW,
hint_bg: colors::CYAN,
};
let hint_alignment = HintAlignment::Leading;
@ -974,8 +974,8 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
{goto3}Barcelona https://en.wikipedia.org/wiki/Barcelona -{fg_reset}{bg_reset}",
goto1 = goto1,
goto3 = goto3,
fg = color::Fg(rendering_colors.text_fg.as_ref()),
bg = color::Bg(rendering_colors.text_bg.as_ref()),
fg = color::Fg(rendering_colors.text_fg),
bg = color::Bg(rendering_colors.text_bg),
fg_reset = color::Fg(color::Reset),
bg_reset = color::Bg(color::Reset),
);
@ -1013,14 +1013,14 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
let default_output_destination = OutputDestination::Tmux;
let rendering_colors = UiColors {
text_fg: Box::new(color::Black),
text_bg: Box::new(color::White),
focused_fg: Box::new(color::Red),
focused_bg: Box::new(color::Blue),
span_fg: Box::new(color::Green),
span_bg: Box::new(color::Magenta),
hint_fg: Box::new(color::Yellow),
hint_bg: Box::new(color::Cyan),
text_fg: colors::BLACK,
text_bg: colors::WHITE,
focused_fg: colors::RED,
focused_bg: colors::BLUE,
span_fg: colors::GREEN,
span_bg: colors::MAGENTA,
hint_fg: colors::YELLOW,
hint_bg: colors::CYAN,
};
let hint_alignment = HintAlignment::Leading;
let hint_style = None;
@ -1046,8 +1046,8 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
{goto3}Barcelona https://en.wikipedia.org/wiki/Barcelona -{fg_reset}{bg_reset}",
goto1 = goto1,
goto3 = goto3,
fg = color::Fg(rendering_colors.text_fg.as_ref()),
bg = color::Bg(rendering_colors.text_bg.as_ref()),
fg = color::Fg(rendering_colors.text_fg),
bg = color::Bg(rendering_colors.text_bg),
fg_reset = color::Fg(color::Reset),
bg_reset = color::Bg(color::Reset)
)
@ -1058,8 +1058,8 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
format!(
"{goto7_1}{span_bg}{span_fg}127.0.0.1{fg_reset}{bg_reset}",
goto7_1 = goto7_1,
span_fg = color::Fg(rendering_colors.span_fg.as_ref()),
span_bg = color::Bg(rendering_colors.span_bg.as_ref()),
span_fg = color::Fg(rendering_colors.span_fg),
span_bg = color::Bg(rendering_colors.span_bg),
fg_reset = color::Fg(color::Reset),
bg_reset = color::Bg(color::Reset)
)
@ -1071,8 +1071,8 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
format!(
"{goto7_1}{hint_bg}{hint_fg}b{fg_reset}{bg_reset}",
goto7_1 = goto7_1,
hint_fg = color::Fg(rendering_colors.hint_fg.as_ref()),
hint_bg = color::Bg(rendering_colors.hint_bg.as_ref()),
hint_fg = color::Fg(rendering_colors.hint_fg),
hint_bg = color::Bg(rendering_colors.hint_bg),
fg_reset = color::Fg(color::Reset),
bg_reset = color::Bg(color::Reset)
)
@ -1083,8 +1083,8 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
format!(
"{goto11_3}{focus_bg}{focus_fg}https://en.wikipedia.org/wiki/Barcelona{fg_reset}{bg_reset}",
goto11_3 = goto11_3,
focus_fg = color::Fg(rendering_colors.focused_fg.as_ref()),
focus_bg = color::Bg(rendering_colors.focused_bg.as_ref()),
focus_fg = color::Fg(rendering_colors.focused_fg),
focus_bg = color::Bg(rendering_colors.focused_bg),
fg_reset = color::Fg(color::Reset),
bg_reset = color::Bg(color::Reset)
)