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

View file

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

View file

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

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
/target /target
**/*.rs.bk **/*.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] [package]
name = "copyrat" name = "copyrat"
version = "0.4.1" version = "0.4.1"
authors = ["graelo <graelo@graelo.cc>"] edition = "2021"
edition = "2018" description = "A tmux plugin for copy-pasting within tmux panes."
description = "This is tmux-copycat on Rust steroids." readme = "README.md"
repository = "https://github.com/graelo/tmux-copyrat"
keywords = ["rust", "tmux", "tmux-plugin", "tmux-copycat"]
license = "MIT" 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] [dependencies]
termion = "1.5" thiserror = "1"
regex = "1.4"
clap = { version = "3.0.0-beta.2", features = ["suggestions", "color", "wrap_help"]} termion = "2"
regex = "1.6"
clap = { version = "4.0", features = ["derive", "wrap_help"]}
sequence_trie = "0.3.6" sequence_trie = "0.3.6"
duct = "0.13" duct = "0.13"
@ -22,3 +31,26 @@ path = "src/bin/copyrat.rs"
[[bin]] [[bin]]
name = "tmux-copyrat" name = "tmux-copyrat"
path = "src/bin/tmux_copyrat.rs" 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 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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) [![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) [![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/rustc-1.60+-blue.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) [![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) [![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
[![demo](https://asciinema.org/a/232775.png?ts=1)](https://asciinema.org/a/232775?autoplay=1) [![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 ## 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 | Testing this kind of integration with tmux is time consuming, so I'll be
|:-------:|:----------:| grateful if you report incompatibilities as you find them.
| 3.0b | ✅ |
| 2.9a | ✅ |
| 2.8 | ❓ | ## The `copyrat` standalone executable
| 2.7 | ❓ |
| 2.6 | ✅ | Although the central binary of this crate is `tmux-copyrat`, the crate also
| 2.5 | ❓ | ships with the `copyrat` executable which provides the same functionality,
| 2.4 | ❓ | minus any tmux dependency or integration and instead reads from stdin.
| 2.3 | ❓ |
| 1.8 | ❓ | You can use `copyrat` to search a span of text that you provide to stdin, à la
| 1.7 | ❓ | [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` ## Standalone `thumbs`
@ -393,38 +192,51 @@ If you want to enjoy terminal hints, you can do things like this without `tmux`:
> git log | pick > git log | pick
``` ```
Or multi selection:
``` ## Run code-coverage
> git log | thumbs -m
1df9fa69c8831ac042c6466af81e65402ee2a007 Install the llvm-tools-preview component and grcov
4897dc4ecbd2ac90b17de95e00e9e75bb540e37f
```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 ## License
- [X] Decouple `tmux-thumbs` from `tmux`
- [ ] Code [Kitty](https://github.com/kovidgoyal/kitty) plugin, now that `thumbs` can run standalone
## 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 at your option.
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. ;)
# 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 set -ex
ci=$(dirname $0) 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" rustup run "$version" "$ci/test_full.sh"
done done

View file

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

View file

@ -1,9 +1,10 @@
#!/usr/bin/env zsh #!/usr/bin/env zsh
# This scripts provides a default configuration for tmux-copyrat options and key bindings. # This scripts provides a default configuration for tmux-copyrat options and
# It is run only once at tmux launch. # 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-keytable "foobar"
# set -g @copyrat-keyswitch "z" # 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' # 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 # Just make sure you first open a named window in the background and provide
# that name to the binary `tmux-copyrat`. # that name to the binary `tmux-copyrat`.
# #
@ -27,7 +29,7 @@
# options and bindings in your `tmux.conf`. # options and bindings in your `tmux.conf`.
CURRENT_DIR="$( cd "$( dirname "$0" )" && pwd )" CURRENT_DIR="$( cd "$( dirname "$0" )" && pwd )"
BINARY="${CURRENT_DIR}/tmux-copyrat" BINARY=${CURRENT_DIR}/tmux-copyrat
# #
@ -35,11 +37,11 @@ BINARY="${CURRENT_DIR}/tmux-copyrat"
# #
setup_option() { setup_option() {
local opt_name=$1 local opt_name=$1
local default_value=$2 local default_value=$2
local current_value=$(tmux show-option -gqv @copyrat-${opt_name}) local current_value=$(tmux show-option -gqv @copyrat-${opt_name})
value=${current_value:-${default_value}} value=${current_value:-${default_value}}
tmux set-option -g @copyrat-${opt_name} ${value} tmux set-option -g @copyrat-${opt_name} ${value}
} }
@ -50,8 +52,9 @@ setup_option "window-name" "[copyrat]"
# Get that window name as a local variable for use in pattern bindings below. # Get that window name as a local variable for use in pattern bindings below.
window_name=$(tmux show-option -gqv @copyrat-window-name) window_name=$(tmux show-option -gqv @copyrat-window-name)
# Sets the keytable for all bindings, providing a default if @copyrat-keytable was not defined. # Sets the keytable for all bindings, providing a default if @copyrat-keytable
# Keytables open a new shortcut space: if 't' is the switcher (see below), prefix + t + <your-shortcut> # was not defined. Keytables open a new shortcut space: if 't' is the switcher
# (see below), prefix + t + <your-shortcut>
setup_option "keytable" "cpyrt" setup_option "keytable" "cpyrt"
# Sets the key to access the keytable: prefix + <key> + <your-shortcut> # Sets the key to access the keytable: prefix + <key> + <your-shortcut>
@ -68,11 +71,11 @@ tmux bind-key ${keyswitch} switch-client -T ${keytable}
# #
setup_pattern_binding() { setup_pattern_binding() {
local key=$1 local key=$1
local pattern_arg="$2" local pattern_arg="$2"
# The default window name `[copyrat]` has to be single quoted because it is # The default window name `[copyrat]` has to be single quoted because it is
# interpreted by the shell when launched by tmux. # interpreted by the shell when launched by tmux.
tmux bind-key -T ${keytable} ${key} new-window -d -n ${window_name} "${BINARY} --window-name '"${window_name}"' --reverse --unique-hint ${pattern_arg}" tmux bind-key -T ${keytable} ${key} new-window -d -n ${window_name} "${BINARY} --window-name '"${window_name}"' --reverse --unique-hint ${pattern_arg}"
} }
# prefix + t + c searches for hex colors #aa00f5 # prefix + t + c searches for hex colors #aa00f5
@ -94,7 +97,7 @@ setup_pattern_binding "p" "--pattern-name path"
# prefix + t + P searches for hex numbers: 0xbedead # prefix + t + P searches for hex numbers: 0xbedead
setup_pattern_binding "P" "--pattern-name pointer-address" setup_pattern_binding "P" "--pattern-name pointer-address"
# prefix + t + q searches for strings inside single|double|backticks # 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 # prefix + t + u searches for URLs
setup_pattern_binding "u" "--pattern-name url" setup_pattern_binding "u" "--pattern-name url"
# prefix + t + U searches for UUIDs # 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 std::io::{self, Read};
use copyrat::{config::basic, run, ui::Selection}; use copyrat::{config::basic, run, ui::Selection};

View file

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

View file

@ -1,14 +1,14 @@
use clap::Clap; use std::fmt::Display;
use std::str::FromStr;
use clap::{ArgAction, Parser, ValueEnum};
use crate::{ use crate::{
error,
textbuf::{alphabet, regexes}, textbuf::{alphabet, regexes},
ui, ui, Error, Result,
}; };
/// Main configuration, parsed from command line. /// Main configuration, parsed from command line.
#[derive(Clap, Debug)] #[derive(Parser, Debug)]
#[clap(author, about, version)] #[clap(author, about, version)]
pub struct Config { pub struct Config {
/// Alphabet to draw hints from. /// Alphabet to draw hints from.
@ -20,57 +20,70 @@ pub struct Config {
/// # Examples /// # Examples
/// ///
/// "qwerty", "dvorak-homerow", "azerty-right-hand". /// "qwerty", "dvorak-homerow", "azerty-right-hand".
#[clap(short = 'k', long, default_value = "dvorak", #[arg(
parse(try_from_str = alphabet::parse_alphabet))] short = 'k',
long,
default_value = "dvorak",
value_parser(alphabet::parse_alphabet)
)]
pub alphabet: alphabet::Alphabet, pub alphabet: alphabet::Alphabet,
/// Use all available regex patterns. /// Use all available regex patterns.
#[clap(short = 'A', long = "--all-patterns")] #[arg(short = 'A', long = "all-patterns")]
pub use_all_patterns: bool, pub use_all_patterns: bool,
/// Pattern names to use ("email", ... see doc). /// 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>, pub named_patterns: Vec<regexes::NamedPattern>,
/// Additional regex patterns ("foo*bar", etc). /// Additional regex patterns ("(foo.*)bar", etc). Must have a capture
#[clap(short = 'X', long = "--custom-pattern")] /// group.
#[arg(short = 'X', long)]
pub custom_patterns: Vec<String>, pub custom_patterns: Vec<String>,
/// Assign hints starting from the bottom of the screen. /// Assign hints starting from the bottom of the screen.
#[clap(short, long)] #[arg(short, long, action = ArgAction::SetTrue)]
pub reverse: bool, pub reverse: bool,
/// Keep the same hint for identical spans. /// Keep the same hint for identical spans.
#[clap(short, long)] #[arg(short, long, action = ArgAction::SetTrue)]
pub unique_hint: bool, pub unique_hint: bool,
/// Move focus back to first/last span. /// Move focus back to first/last span.
#[clap(short = 'w', long)] #[arg(short = 'w', long, action = ArgAction::SetTrue)]
pub focus_wrap_around: bool, pub focus_wrap_around: bool,
#[clap(flatten)] #[command(flatten)]
pub colors: ui::colors::UiColors, pub colors: ui::colors::UiColors,
/// Align hint with its span. /// 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, pub hint_alignment: ui::HintAlignment,
/// Optional hint styling. /// Optional hint styling.
/// ///
/// Underline or surround the hint for increased visibility. /// Underline or surround the hint for increased visibility.
/// If not provided, only the hint colors will be used. /// If not provided, only the hint colors will be used.
#[clap(short = 's', long, arg_enum)] #[arg(short = 's', long = "hint-style", rename_all = "lowercase", value_enum)]
pub hint_style: Option<HintStyleArg>, pub hint_style_arg: Option<HintStyleArg>,
/// Chars surrounding each hint, used with `Surround` style. /// Chars surrounding each hint, used with `Surround` style.
#[clap(long, default_value = "{}", #[clap(
parse(try_from_str = parse_chars))] long,
pub hint_surroundings: (char, char), // default_value_t = HintSurroundingsArg{open: '{', close: '}'},
default_value = "{}",
value_parser(try_parse_chars)
)]
pub hint_surroundings: HintSurroundingsArg,
} }
/// Type introduced due to parsing limitation, /// Type introduced due to parsing limitation,
/// as we cannot directly parse into ui::HintStyle. /// as we cannot directly parse tuples into ui::HintStyle.
#[derive(Debug, Clap)] #[derive(Debug, Clone, ValueEnum)]
pub enum HintStyleArg { pub enum HintStyleArg {
Bold, Bold,
Italic, Italic,
@ -78,28 +91,44 @@ pub enum HintStyleArg {
Surround, Surround,
} }
impl FromStr for HintStyleArg { #[derive(Debug, Clone)]
type Err = error::ParseError; pub struct HintSurroundingsArg {
pub open: char,
pub close: char,
}
fn from_str(s: &str) -> Result<Self, error::ParseError> { impl Display for HintSurroundingsArg {
match s { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
"bold" => Ok(HintStyleArg::Bold), write!(f, "{}{}", self.open, self.close)
"italic" => Ok(HintStyleArg::Italic),
"underline" => Ok(HintStyleArg::Underline),
"surrond" => Ok(HintStyleArg::Surround),
_ => Err(error::ParseError::ExpectedString(String::from(
"bold, italic, underline or surround",
))),
}
} }
} }
/// Try to parse a `&str` into a tuple of `char`s. /// 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 { if src.chars().count() != 2 {
return Err(error::ParseError::ExpectedSurroundingPair); return Err(Error::ExpectedSurroundingPair);
} }
let chars: Vec<char> = src.chars().collect(); 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::collections::HashMap;
use std::fmt; use std::fmt;
use std::str::FromStr;
use clap::{Parser, ValueEnum};
use super::basic; 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 /// Extended configuration for handling Tmux-specific configuration (options
/// and outputs). This is only used by `tmux-copyrat` and parsed from command /// and outputs). This is only used by `tmux-copyrat` and parsed from command
/// line.. /// line.
#[derive(Clap, Debug)] #[derive(Parser, Debug)]
#[clap(author, about, version)] #[clap(author, about, version)]
pub struct ConfigExt { pub struct ConfigExt {
/// Don't read options from Tmux. /// Don't read options from Tmux.
@ -30,13 +30,19 @@ pub struct ConfigExt {
pub window_name: String, pub window_name: String,
/// Capture visible area or entire pane history. /// 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, pub capture_region: CaptureRegion,
/// Name of the copy-to-clipboard executable. /// Name of the copy-to-clipboard executable.
/// ///
/// If during execution, the output destination is set to be clipboard, /// If during execution, the output destination is set to be clipboard,
/// then copyrat will pipe the selected text to this executable. /// 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")] #[clap(long, default_value = "pbcopy")]
pub clipboard_exe: String, pub clipboard_exe: String,
@ -46,46 +52,54 @@ pub struct ConfigExt {
} }
impl ConfigExt { impl ConfigExt {
pub fn initialize() -> Result<ConfigExt, error::ParseError> { pub fn initialize() -> Result<ConfigExt> {
let mut config_ext = ConfigExt::parse(); let mut config_ext = ConfigExt::parse();
if !config_ext.ignore_tmux_options { if !config_ext.ignore_tmux_options {
let tmux_options: HashMap<String, String> = tmux::get_options("@copyrat-")?; let tmux_options: HashMap<String, String> = tmux::get_options("@copyrat-")?;
// Override default values with those coming from tmux. // 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 { for (name, value) in &tmux_options {
match name.as_ref() { match name.as_ref() {
"@copyrat-capture-region" => { "@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" => { "@copyrat-alphabet" => {
wrapped.alphabet = alphabet::parse_alphabet(value)?; inner.alphabet = alphabet::parse_alphabet(value)?;
} }
"@copyrat-reverse" => { "@copyrat-reverse" => {
wrapped.reverse = value.parse::<bool>()?; inner.reverse = value.parse::<bool>()?;
} }
"@copyrat-unique-hint" => { "@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-fg" => inner.colors.span_fg = ui::colors::parse_color(value)?,
"@copyrat-span-bg" => wrapped.colors.span_bg = ui::colors::parse_color(value)?, "@copyrat-span-bg" => inner.colors.span_bg = ui::colors::parse_color(value)?,
"@copyrat-focused-fg" => { "@copyrat-focused-fg" => {
wrapped.colors.focused_fg = ui::colors::parse_color(value)? inner.colors.focused_fg = ui::colors::parse_color(value)?
} }
"@copyrat-focused-bg" => { "@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-fg" => inner.colors.hint_fg = ui::colors::parse_color(value)?,
"@copyrat-hint-bg" => wrapped.colors.hint_bg = ui::colors::parse_color(value)?, "@copyrat-hint-bg" => inner.colors.hint_bg = ui::colors::parse_color(value)?,
"@copyrat-hint-alignment" => { "@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" => { "@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. // 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 { pub enum CaptureRegion {
/// The entire history. /// 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, EntireHistory,
/// The visible area. /// The visible area.
VisibleArea, VisibleArea,
@ -112,20 +126,6 @@ pub enum CaptureRegion {
//Region(i32, i32), //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 /// Describes the type of buffer the selected should be copied to: either a
/// tmux buffer or the system clipboard. /// tmux buffer or the system clipboard.
#[derive(Clone)] #[derive(Clone)]

View file

@ -1,50 +1,43 @@
use std::fmt; // use std::fmt;
#[derive(Debug)] #[derive(thiserror::Error, Debug)]
pub enum ParseError { pub enum Error {
#[error("Expected 2 chars")]
ExpectedSurroundingPair, ExpectedSurroundingPair,
#[error("Unknown alphabet")]
UnknownAlphabet, UnknownAlphabet,
#[error("Unknown ANSI color name: allowed values are magenta, cyan, black, ...")]
UnknownColor, UnknownColor,
#[error("Unknown pattern name")]
UnknownPatternName, UnknownPatternName,
#[error("Expected a pane id marker")]
ExpectedPaneIdMarker, 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), ExpectedString(String),
ProcessFailure(String),
}
impl fmt::Display for ParseError { #[error("Expected the value to be within `{0}`")]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { ExpectedEnumVariant(String),
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),
}
}
}
impl From<std::num::ParseIntError> for ParseError { #[error("IOError: `{source}`")]
fn from(error: std::num::ParseIntError) -> Self { Io {
ParseError::ExpectedInt(error) #[from]
} source: std::io::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())
}
} }

View file

@ -4,6 +4,9 @@ pub mod textbuf;
pub mod tmux; pub mod tmux;
pub mod ui; 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`. /// Run copyrat on an input string `buffer`, configured by `Opt`.
/// ///
/// # Note /// # Note
@ -11,7 +14,7 @@ pub mod ui;
/// Maybe the decision to take ownership of the buffer is a bit bold. /// 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> { pub fn run(lines: &[&str], opt: &config::basic::Config) -> Option<ui::Selection> {
let model = textbuf::Model::new( let model = textbuf::Model::new(
&lines, lines,
&opt.alphabet, &opt.alphabet,
opt.use_all_patterns, opt.use_all_patterns,
&opt.named_patterns, &opt.named_patterns,
@ -24,19 +27,6 @@ pub fn run(lines: &[&str], opt: &config::basic::Config) -> Option<ui::Selection>
return None; 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 default_output_destination = config::extended::OutputDestination::Tmux;
let selection: Option<ui::Selection> = { let selection: Option<ui::Selection> = {
@ -46,7 +36,7 @@ pub fn run(lines: &[&str], opt: &config::basic::Config) -> Option<ui::Selection>
default_output_destination, default_output_destination,
&opt.colors, &opt.colors,
&opt.hint_alignment, &opt.hint_alignment,
hint_style, opt.hint_style(),
); );
ui.present() ui.present()

View file

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

View file

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

View file

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

View file

@ -2,7 +2,7 @@
//! //!
//! All patterns must have one capture group. The first group is used. //! 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] = pub(super) const EXCLUDE_PATTERNS: [(&str, &str); 1] =
[("ansi_colors", r"[[:cntrl:]]\[([0-9]{1,2};)?([0-9]{1,2})?m")]; [("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"\[[^]]*\]\(([^)]+)\)"), ("markdown-url", r"\[[^]]*\]\(([^)]+)\)"),
( (
"url", "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"), ("email", r"\b([A-z0-9._%+-]+@[A-z0-9.-]+\.[A-z]{2,})\b"),
("diff-a", r"--- a/([^ ]+)"), ("diff-a", r"--- a/([^ ]+)"),
@ -42,18 +42,18 @@ pub(super) const PATTERNS: [(&str, &str); 20] = [
), ),
("quoted-single", r#"'([^']+)'"#), ("quoted-single", r#"'([^']+)'"#),
("quoted-double", r#""([^"]+)""#), ("quoted-double", r#""([^"]+)""#),
("quoted-tick", r#"`([^`]+)`"#), ("quoted-backtick", r#"`([^`]+)`"#),
("digits", r"([0-9]{4,})"), ("digits", r"([0-9]{4,})"),
]; ];
/// Type-safe string Pattern Name (newtype). /// Type-safe string Pattern Name (newtype).
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct NamedPattern(pub String, pub String); pub struct NamedPattern(pub String, pub String);
/// Parse a name string into `NamedPattern`, used during CLI parsing. /// 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) { match PATTERNS.iter().find(|&(name, _pattern)| name == &src) {
Some((name, pattern)) => Ok(NamedPattern(name.to_string(), pattern.to_string())), 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 //! The main use cases are running Tmux commands & parsing Tmux panes
//! information. //! information.
use regex::Regex;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt; use std::fmt;
use std::str::FromStr; use std::str::FromStr;
use crate::config::extended::CaptureRegion; use regex::Regex;
use crate::error::ParseError;
#[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 { pub struct Pane {
/// Pane identifier, e.g. `%37`. /// Pane identifier, e.g. `%37`.
pub id: PaneId, pub id: PaneId,
@ -30,7 +33,7 @@ pub struct Pane {
} }
impl FromStr for Pane { impl FromStr for Pane {
type Err = ParseError; type Err = Error;
/// Parse a string containing tmux panes status into a new `Pane`. /// 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, /// For definitions, look at `Pane` type,
/// and at the tmux man page for definitions. /// 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(); let items: Vec<&str> = src.split(':').collect();
assert_eq!(items.len(), 5, "tmux should have returned 5 items per line"); 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 /// 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`. /// 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 mut args_str = format!("capture-pane -t {pane_id} -J -p", pane_id = self.id);
let region_str = match region { let region_str = match region {
@ -125,17 +128,17 @@ impl Pane {
} }
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq, Eq)]
pub struct PaneId(String); pub struct PaneId(String);
impl FromStr for PaneId { impl FromStr for PaneId {
type Err = ParseError; type Err = Error;
/// Parse into PaneId. The `&str` must be start with '%' /// Parse into PaneId. The `&str` must be start with '%'
/// followed by a `u16`. /// 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('%') { if !src.starts_with('%') {
return Err(ParseError::ExpectedPaneIdMarker); return Err(Error::ExpectedPaneIdMarker);
} }
let id = src[1..].parse::<u16>()?; let id = src[1..].parse::<u16>()?;
let id = format!("%{}", id); let id = format!("%{}", id);
@ -156,7 +159,7 @@ impl fmt::Display for PaneId {
} }
/// Returns a list of `Pane` from the current tmux session. /// 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![ let args = vec![
"list-panes", "list-panes",
"-F", "-F",
@ -165,12 +168,12 @@ pub fn available_panes() -> Result<Vec<Pane>, ParseError> {
let output = duct::cmd("tmux", &args).read()?; let output = duct::cmd("tmux", &args).read()?;
// Each call to `Pane::parse` returns a `Result<Pane, _>`. All results // Each call to `Pane::parse` returns a `Result<Pane>`. All results
// are collected into a Result<Vec<Pane>, _>, thanks to `collect()`. // are collected into a Result<Vec<Pane>>, thanks to `collect()`.
let result: Result<Vec<Pane>, ParseError> = output let result: Result<Vec<Pane>> = output
.trim_end() // trim last '\n' as it would create an empty line .trim_end() // trim last '\n' as it would create an empty line
.split('\n') .split('\n')
.map(|line| Pane::from_str(line)) .map(Pane::from_str) // .map(|line| Pane::from_str(line))
.collect(); .collect();
result result
@ -182,7 +185,7 @@ pub fn available_panes() -> Result<Vec<Pane>, ParseError> {
/// ///
/// # Example /// # Example
/// ```get_options("@copyrat-")``` /// ```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 output = duct::cmd!("tmux", "show-options", "-g").read()?;
let lines: Vec<&str> = output.split('\n').collect(); 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) Ok(args)
} }
/// Ask tmux to swap the current Pane with the target_pane (uses Tmux format). /// Asks tmux to swap the current Pane with the target_pane (uses Tmux format).
pub fn swap_pane_with(target_pane: &str) -> Result<(), ParseError> { pub fn swap_pane_with(target_pane: &str) -> Result<()> {
// -Z: keep the window zoomed if it was zoomed. // -Z: keep the window zoomed if it was zoomed.
duct::cmd!("tmux", "swap-pane", "-Z", "-s", target_pane).run()?; 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)] #[cfg(test)]
mod tests { mod tests {
use super::Pane; use super::*;
use super::PaneId;
use crate::error;
use std::str::FromStr; use std::str::FromStr;
#[test] #[test]
fn test_parse_pass() { fn test_parse_pass() {
let output = vec!["%52:false:62:3:false", "%53:false:23::true"]; let output = vec!["%52:false:62:3:false", "%53:false:23::true"];
let panes: Result<Vec<Pane>, error::ParseError> = let panes: Result<Vec<Pane>> = output.iter().map(|&line| Pane::from_str(line)).collect();
output.iter().map(|&line| Pane::from_str(line)).collect();
let panes = panes.expect("Could not parse tmux panes"); let panes = panes.expect("Could not parse tmux panes");
let expected = vec![ let expected = vec![

View file

@ -1,28 +1,79 @@
use crate::error; use std::fmt;
use clap::Clap; use std::str::FromStr;
use termion::color;
pub fn parse_color(src: &str) -> Result<Box<dyn color::Color>, error::ParseError> { use clap::Args;
match src { use termion::color as tcolor;
"black" => Ok(Box::new(color::Black)),
"red" => Ok(Box::new(color::Red)), use crate::{Error, Result};
"green" => Ok(Box::new(color::Green)),
"yellow" => Ok(Box::new(color::Yellow)), #[derive(Debug, Clone, Copy)]
"blue" => Ok(Box::new(color::Blue)), pub struct Color(Option<u8>);
"magenta" => Ok(Box::new(color::Magenta)),
"cyan" => Ok(Box::new(color::Cyan)), impl tcolor::Color for Color {
"white" => Ok(Box::new(color::White)), #[inline]
"bright-black" | "brightblack" => Ok(Box::new(color::LightBlack)), fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
"bright-red" | "brightred" => Ok(Box::new(color::LightRed)), match self.0 {
"bright-green" | "brightgreen" => Ok(Box::new(color::LightGreen)), Some(value) => write!(f, "\x1B[38;5;{}m", value),
"bright-yellow" | "brightyellow" => Ok(Box::new(color::LightYellow)), None => write!(f, "\x1B[39m"),
"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),
} }
#[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)] #[cfg(test)]
@ -30,20 +81,27 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn span_color() { fn span_color_fg() {
let text1 = format!( let actual = format!("{}{}", tcolor::Fg(Color::from_str("green").unwrap()), "foo");
"{}{}", let expected = format!("{}{}", tcolor::Fg(tcolor::Green), "foo");
color::Fg(parse_color("green").unwrap().as_ref()),
"foo"
);
let text2 = format!("{}{}", color::Fg(color::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] #[test]
fn no_span_color() { 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. /// - `focus_*` colors are used to render the currently focused text span.
/// - `normal_*` colors are used to render other text spans. /// - `normal_*` colors are used to render other text spans.
/// - `hint_*` colors are used to render the hints. /// - `hint_*` colors are used to render the hints.
#[derive(Clap, Debug)] #[derive(Args, Debug)]
#[clap(about)] // Needed to avoid this doc comment to be used as overall `about`. // #[clap(about)] // Needed to avoid this doc comment to be used as overall `about`.
pub struct UiColors { pub struct UiColors {
/// Foreground color for base text. /// Foreground color for base text.
#[clap(long, default_value = "bright-cyan", parse(try_from_str = parse_color))] #[arg(long, default_value = "bright-cyan", value_parser(parse_color))]
pub text_fg: Box<dyn color::Color>, pub text_fg: Color,
/// Background color for base text. /// Background color for base text.
#[clap(long, default_value = "none", parse(try_from_str = parse_color))] #[clap(long, default_value = "none", value_parser(parse_color))]
pub text_bg: Box<dyn color::Color>, pub text_bg: Color,
/// Foreground color for spans. /// Foreground color for spans.
#[clap(long, default_value = "blue", #[clap(long, default_value = "blue", value_parser(parse_color))]
parse(try_from_str = parse_color))] pub span_fg: Color,
pub span_fg: Box<dyn color::Color>,
/// Background color for spans. /// Background color for spans.
#[clap(long, default_value = "none", #[clap(long, default_value = "none", value_parser(parse_color))]
parse(try_from_str = parse_color))] pub span_bg: Color,
pub span_bg: Box<dyn color::Color>,
/// Foreground color for the focused span. /// Foreground color for the focused span.
#[clap(long, default_value = "magenta", #[clap(long, default_value = "magenta", value_parser(parse_color))]
parse(try_from_str = parse_color))] pub focused_fg: Color,
pub focused_fg: Box<dyn color::Color>,
/// Background color for the focused span. /// Background color for the focused span.
#[clap(long, default_value = "none", #[clap(long, default_value = "none", value_parser(parse_color))]
parse(try_from_str = parse_color))] pub focused_bg: Color,
pub focused_bg: Box<dyn color::Color>,
/// Foreground color for hints. /// Foreground color for hints.
#[clap(long, default_value = "yellow", #[clap(long, default_value = "yellow", value_parser(parse_color))]
parse(try_from_str = parse_color))] pub hint_fg: Color,
pub hint_fg: Box<dyn color::Color>,
/// Background color for hints. /// Background color for hints.
#[clap(long, default_value = "none", #[clap(long, default_value = "none", value_parser(parse_color))]
parse(try_from_str = parse_color))] pub hint_bg: Color,
pub hint_bg: Box<dyn color::Color>,
} }

View file

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