mirror of
https://github.com/Morantron/tmux-fingers.git
synced 2026-09-10 07:26:32 -04:00
Compare commits
183 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc3c750b8d | ||
|
|
95df1958aa | ||
|
|
abf70872ef | ||
|
|
7fd9896491 | ||
|
|
1bcbcaa2af | ||
|
|
fbc9ed8dd6 | ||
|
|
5409aee975 | ||
|
|
02a35b246c | ||
|
|
ae26058e6c | ||
|
|
c77287b56d | ||
|
|
79d0355585 | ||
|
|
f99ceba76f | ||
|
|
336a5c6e90 | ||
|
|
7308c31c05 | ||
|
|
d52c6c07a4 | ||
|
|
38fe26bdff | ||
|
|
8433feea47 | ||
|
|
5cf72accd9 | ||
|
|
8b56badada | ||
|
|
3190d698d5 | ||
|
|
e4280e1bec | ||
|
|
a168542a83 | ||
|
|
b43b51ffad | ||
|
|
9a0cff4b50 | ||
|
|
dabef3697b | ||
|
|
5927132aee | ||
|
|
29f90165fa | ||
|
|
dce0319efd | ||
|
|
64c35e7893 | ||
|
|
99dafe680d | ||
|
|
ccdd8df283 | ||
|
|
52fe76cfd1 | ||
|
|
f97187cd93 | ||
|
|
be6ef53751 | ||
|
|
2239925824 | ||
|
|
56f493f644 | ||
|
|
3dae735cb3 | ||
|
|
8451588fd4 | ||
|
|
eeb9fc3310 | ||
|
|
0c118266b8 | ||
|
|
2795f8aaa7 | ||
|
|
7fa3cc3a6f | ||
|
|
56d447282d | ||
|
|
ec73be73c4 | ||
|
|
ff9954563b | ||
|
|
edd2972a82 | ||
|
|
c07e1baf28 | ||
|
|
6677dd1701 | ||
|
|
d05ec8817f | ||
|
|
e3561d5dfa | ||
|
|
a91fd35bf3 | ||
|
|
9eec2a9d23 | ||
|
|
1456f54f21 | ||
|
|
39c561bac3 | ||
|
|
809422b139 | ||
|
|
8c45c60456 | ||
|
|
7b46f6cd1c | ||
|
|
43f792b8a2 | ||
|
|
ea0447f05a | ||
|
|
ae2e1d5cd3 | ||
|
|
5f0423088c | ||
|
|
d8bb10e2a2 | ||
|
|
bbf320b0c7 | ||
|
|
8431420e34 | ||
|
|
ea979d5e4a | ||
|
|
e5f6fb0690 | ||
|
|
963ca74c9e | ||
|
|
e5d8f3ce42 | ||
|
|
e4b4741f5a | ||
|
|
62ac2ec93a | ||
|
|
d67503fde5 | ||
|
|
1dcfc4815f | ||
|
|
f5faacd9da | ||
|
|
fa3947d487 | ||
|
|
1e4708a903 | ||
|
|
1439a8542b | ||
|
|
e7a589600c | ||
|
|
abdaa1eee0 | ||
|
|
2697db501b | ||
|
|
c73bed42ee | ||
|
|
6f99edc1a9 | ||
|
|
41dbe3dba5 | ||
|
|
bbbca3cd4d | ||
|
|
a668575a13 | ||
|
|
d04a1185e8 | ||
|
|
33df35c406 | ||
|
|
e2283d61e4 | ||
|
|
10deafaa5e | ||
|
|
645f92e26e | ||
|
|
75f0c7c53f | ||
|
|
316a1dfa9f | ||
|
|
e52717b1af | ||
|
|
6ccfeb7b87 | ||
|
|
c174b1919d | ||
|
|
8698822653 | ||
|
|
09e7c1791e | ||
|
|
c694c1a8dd | ||
|
|
33e2f0e0e2 | ||
|
|
ab8c4be8c0 | ||
|
|
5ec5977201 | ||
|
|
d42466771f | ||
|
|
dacb192171 | ||
|
|
4af0d7a48c | ||
|
|
9d80b2dd26 | ||
|
|
b4bb3c13e4 | ||
|
|
c257f9388e | ||
|
|
0f2d8b9dcf | ||
|
|
834f392d3d | ||
|
|
7f6a019042 | ||
|
|
9d609897a7 | ||
|
|
7ba9307102 | ||
|
|
d7fcf90f8d | ||
|
|
9d29d3467d | ||
|
|
296cc8e49e | ||
|
|
b90d7c4b3c | ||
|
|
5cd719e5ce | ||
|
|
9b5411120f | ||
|
|
7a1d183b81 | ||
|
|
36b0be2002 | ||
|
|
59b7743293 | ||
|
|
3e1d0a8d85 | ||
|
|
83bf475b97 | ||
|
|
5b1b380c99 | ||
|
|
f72330eff9 | ||
|
|
5d52ace04d | ||
|
|
1cc13c9ae3 | ||
|
|
1cd1119970 | ||
|
|
f5c8a14560 | ||
|
|
c8b911e2c7 | ||
|
|
060b342ee3 | ||
|
|
ad0ae1e90f | ||
|
|
99c20be5b2 | ||
|
|
97d1ddd467 | ||
|
|
9b393e5757 | ||
|
|
27c5512bc2 | ||
|
|
2996ef2644 | ||
|
|
3c7c6a8673 | ||
|
|
03f0048468 | ||
|
|
7e693d40cd | ||
|
|
4d3fa1eb30 | ||
|
|
47d986b8a2 | ||
|
|
f6813b8b16 | ||
|
|
4ba2ec3125 | ||
|
|
a30f2fb566 | ||
|
|
65a3dd2665 | ||
|
|
c186efa73c | ||
|
|
cd3241ad42 | ||
|
|
05a9ece2d9 | ||
|
|
1be65bc1dc | ||
|
|
26d1f67aa9 | ||
|
|
7e408344b2 | ||
|
|
680d801770 | ||
|
|
8cfc2bd1ad | ||
|
|
6718322da9 | ||
|
|
27ffac083d | ||
|
|
eb3c21eae6 | ||
|
|
f079539a65 | ||
|
|
cda4b68f8c | ||
|
|
016c1fa8da | ||
|
|
d6ae53daac | ||
|
|
13c46d71eb | ||
|
|
65af29eba9 | ||
|
|
da3cd9f267 | ||
|
|
5a891fde67 | ||
|
|
2bb5bd2d5b | ||
|
|
fcc4a3ad6c | ||
|
|
b154d4800f | ||
|
|
6ad4c5530e | ||
|
|
a47a9b2809 | ||
|
|
55c47402b3 | ||
|
|
abca481d49 | ||
|
|
3ef6184fde | ||
|
|
cb64f9b859 | ||
|
|
193d01601f | ||
|
|
b2f7567d0a | ||
|
|
c9c6d8e79b | ||
|
|
d65a73fb33 | ||
|
|
485d0fbd8a | ||
|
|
4a93da7c0e | ||
|
|
579a823729 | ||
|
|
28c46301da | ||
|
|
f15d9512ea | ||
|
|
5edb746f8c |
12
.github/ISSUE_TEMPLATE.md
vendored
Normal file
12
.github/ISSUE_TEMPLATE.md
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<!--
|
||||
Note: Please search to see if an issue already exists for the bug you encountered.
|
||||
-->
|
||||
|
||||
### Description
|
||||
|
||||
<!-- Description of issue you are experiencing -->
|
||||
|
||||
### tmux-fingers info output
|
||||
|
||||
<!-- Output of the following command: -->
|
||||
<!-- tmux run "#{@fingers-cli} info" -->
|
||||
34
.github/workflows/deployment.yml
vendored
34
.github/workflows/deployment.yml
vendored
|
|
@ -9,13 +9,15 @@ jobs:
|
|||
dist_linux:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: crystallang/crystal:latest-alpine
|
||||
image: crystallang/crystal:1.19-alpine
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Update Libs
|
||||
run: apk add --update --upgrade --no-cache --force-overwrite libxml2-dev yaml-dev yaml-static
|
||||
- name: Build
|
||||
run: shards build --production --release --static --no-debug
|
||||
env:
|
||||
WIZARD_INSTALLATION_METHOD: download-binary
|
||||
- name: Upload
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
env:
|
||||
|
|
@ -25,6 +27,36 @@ jobs:
|
|||
asset_path: ./bin/tmux-fingers
|
||||
asset_name: tmux-fingers-${{ github.event.release.tag_name }}-linux-x86_64
|
||||
asset_content_type: binary/octet-stream
|
||||
dist_macos_arm64:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Crystal
|
||||
uses: crystal-lang/install-crystal@v1
|
||||
with:
|
||||
crystal: "1.19"
|
||||
- name: Install static lib dependencies
|
||||
run: brew install bdw-gc pcre2
|
||||
- name: Prepare static libs
|
||||
run: |
|
||||
mkdir -p bin
|
||||
ln -sf $(brew --prefix bdw-gc)/lib/libgc.a bin/libgc.a
|
||||
ln -sf $(brew --prefix pcre2)/lib/libpcre2-8.a bin/libpcre2-8.a
|
||||
- name: Build
|
||||
run: |
|
||||
shards build --production --release --no-debug \
|
||||
--link-flags="-L $(pwd)/bin $(pwd)/bin/libgc.a $(pwd)/bin/libpcre2-8.a"
|
||||
env:
|
||||
WIZARD_INSTALLATION_METHOD: download-binary
|
||||
- name: Upload
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ github.event.release.upload_url }}
|
||||
asset_path: ./bin/tmux-fingers
|
||||
asset_name: tmux-fingers-${{ github.event.release.tag_name }}-macos-arm64
|
||||
asset_content_type: binary/octet-stream
|
||||
dist_homebrew:
|
||||
name: Bump Homebrew formula
|
||||
runs-on: ubuntu-latest
|
||||
|
|
|
|||
93
.github/workflows/pull_request.yml
vendored
Normal file
93
.github/workflows/pull_request.yml
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
name: Pull Request
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['develop', 'master']
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
spec:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Create /app symlink
|
||||
run: sudo ln -s $GITHUB_WORKSPACE /app
|
||||
|
||||
- name: Install dependencies for tmux and benchmarks
|
||||
run: sudo apt-get update && sudo apt-get install -y libevent-dev libncurses-dev build-essential bison pkg-config hyperfine
|
||||
|
||||
- name: Cache tmux installations
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /opt/tmux-*
|
||||
key: tmux-versions-${{ hashFiles('spec/install-tmux-versions.sh') }}
|
||||
|
||||
- name: Install tmux versions
|
||||
run: sudo bash spec/install-tmux-versions.sh
|
||||
|
||||
- name: Symlink use-tmux.sh
|
||||
run: sudo ln -s $GITHUB_WORKSPACE/spec/use-tmux.sh /opt/use-tmux.sh
|
||||
|
||||
- uses: crystal-lang/install-crystal@v1
|
||||
with:
|
||||
crystal: 1.19
|
||||
|
||||
- name: Install dependencies
|
||||
run: shards install
|
||||
|
||||
- name: Run tests
|
||||
run: crystal spec
|
||||
|
||||
benchmark:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Create /app symlink
|
||||
run: sudo ln -s $GITHUB_WORKSPACE /app
|
||||
|
||||
- name: Install dependencies for tmux and benchmarks
|
||||
run: sudo apt-get update && sudo apt-get install -y libevent-dev libncurses-dev build-essential bison pkg-config hyperfine
|
||||
|
||||
- name: Cache tmux installations
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /opt/tmux-*
|
||||
key: tmux-versions-${{ hashFiles('spec/install-tmux-versions.sh') }}
|
||||
|
||||
- name: Install tmux versions
|
||||
run: sudo bash spec/install-tmux-versions.sh
|
||||
|
||||
- name: Symlink use-tmux.sh
|
||||
run: sudo ln -s $GITHUB_WORKSPACE/spec/use-tmux.sh /opt/use-tmux.sh
|
||||
|
||||
- uses: crystal-lang/install-crystal@v1
|
||||
with:
|
||||
crystal: 1.19
|
||||
|
||||
- name: Install dependencies
|
||||
run: shards install
|
||||
|
||||
- name: Fetch main branches
|
||||
run: |
|
||||
git fetch origin master develop
|
||||
git branch -f master origin/master 2>/dev/null || true
|
||||
git branch -f develop origin/develop 2>/dev/null || true
|
||||
|
||||
- name: Setup tmux version for benchmark
|
||||
run: |
|
||||
sudo /opt/use-tmux.sh 3.3a
|
||||
which tmux
|
||||
tmux -V
|
||||
|
||||
- name: Run benchmark
|
||||
run: crystal run dev/benchmark.cr -- ${GITHUB_SHA} develop
|
||||
env:
|
||||
PATH: /usr/local/bin:/usr/bin:${{ env.PATH }}
|
||||
BENCHMARK_RUNS: 250
|
||||
144
CHANGELOG.md
144
CHANGELOG.md
|
|
@ -1,4 +1,146 @@
|
|||
## 2.0.2 - 27 Sep 2023
|
||||
## 2.7.1 - 09 Jun 2026
|
||||
|
||||
* fix parsing of @fingers-enabled-builtin-patterns ( Fixes #176 )
|
||||
* update install-wizard and readme to show that shards is required
|
||||
* relax parsing of --patterns flag in cli
|
||||
|
||||
## 2.7.0 - 31 May 2026
|
||||
|
||||
* improved performance: ~30% faster
|
||||
* added macOS arm64 binary to the release/download pipeline
|
||||
* added kubernetes-pod builtin pattern for pod names
|
||||
* fixed clipboard integration in WSL
|
||||
* fixed various styling rendering issues related to submatches and backdrop
|
||||
* removed tput dependency
|
||||
* improved error handling
|
||||
* refactored option parsing, now with stricter validation
|
||||
* updated to crystal 1.19
|
||||
|
||||
## 2.6.2 - 15 Feb 2026
|
||||
|
||||
* Fix line jumping and backdrop-style rendering issues related with tabs,
|
||||
double width characters, and zoomed panes.
|
||||
|
||||
## 2.6.1 - 27 Jan 2026
|
||||
|
||||
* Fix: close stdin before waiting for load-buffer command ( Fixes #157 )
|
||||
* handle tab characters properly by prefilling line ( Fixes #155 and #140 )
|
||||
* add SHELL variable to info command, and properly handle unset env vars
|
||||
* misc dev improvements
|
||||
|
||||
## 2.6.0 - 05 Jan 2026
|
||||
|
||||
* Added new @fingers-enable-bindings option ( Fixes #149 )
|
||||
* Fixed colors in tmux 3.6a
|
||||
* Fixed case where hint is longer than text to highlight ( Fixes #139 and #154 )
|
||||
|
||||
## 2.5.1 - 04 Aug 2025
|
||||
|
||||
* Use pane_current_path with presence when chdir ( thanks @na0x2c6 ! )
|
||||
|
||||
## 2.5.0 - 17 Jul 2025
|
||||
|
||||
* Added new @fingers-use-system-clipboard option ( thanks @purajit ! )
|
||||
|
||||
## 2.4.1 - 18 May 2025
|
||||
|
||||
* Fix active pane restore when passing a format as target.
|
||||
|
||||
## 2.4.0 - 04 Apr 2025
|
||||
|
||||
* Add support to tmux target-pane tokens, which allows to target other panes
|
||||
rather than the active one like: adjacent panes, previous pane, etc... ( Fixes #138 )
|
||||
|
||||
## 2.3.3 - 06 Feb 2025
|
||||
|
||||
* Fix :paste: action again, this time for real. Working in copy mode and non-copy mode. ( Fixes #137 )
|
||||
|
||||
## 2.3.2 - 02 Feb 2025
|
||||
|
||||
* :paste: will now exit copy mode right before pasting.
|
||||
* Documentation improvements.
|
||||
* Properly handle single quotes in url pattern.
|
||||
|
||||
## 2.3.1 - 10 Dec 2024
|
||||
|
||||
* Fix :paste: action.
|
||||
|
||||
## 2.3.0 - 09 Dec 2024
|
||||
|
||||
* Added --(main|ctrl|alt|shift)-action cli options.
|
||||
* Add info command to show information.
|
||||
* Improved git-status builtin pattern
|
||||
* Fixed root key table not being properly restored ( Fixes #130 )
|
||||
* Updated to Crystal 1.14.
|
||||
* Various perfomance improvements (from 50ms to 40ms on my machine :tm:)
|
||||
* Improved benchmark and development scripts.
|
||||
|
||||
## 2.2.2 - 14 Aug 2024
|
||||
|
||||
* Fixed error in multi-user environments.
|
||||
* Added XDG dirs, logs are now in ~/.local/state/tmux-fingers/fingers.log instead of /tmp/fingers.log.
|
||||
* Action stderr is now logged to ~/.local/state/tmux-fingers/action-stderr
|
||||
|
||||
## 2.2.1 - 29 Jul 2024
|
||||
|
||||
* Tweaks to version detection logic ( Fixes #125 ).
|
||||
|
||||
## 2.2.0 - 21 Jul 2024
|
||||
|
||||
* Add new option @fingers-enabled-builtin-patterns ( Fixes #19 ).
|
||||
* New way of setting up tmux-fingers bindings, with command line option to allow using only specific patterns (built-in or custom) ( Fixes #117 )
|
||||
* Fix issue where zoom panes would be unzoomed when closing ( Fixes #123 ).
|
||||
* Open tmux-fingers in same path as target pane path ( Fixes #120 ).
|
||||
|
||||
## 2.1.5 - 02 May 2024
|
||||
|
||||
* User defined patterns now take precedence over built-in ones.
|
||||
* Fix blank screen under certain circumstances due to incorrect handling of capture groups.
|
||||
|
||||
## 2.1.4 - 08 Mar 2024
|
||||
|
||||
* Fixed "No last pane" error when using "tmux last-pane" ( Fixes #48 )
|
||||
|
||||
## 2.1.3 - 31 Jan 2024
|
||||
|
||||
* Fix "Too many matches" exception ( fixes #112 ).
|
||||
|
||||
## 2.1.2 - 19 Jan 2024
|
||||
|
||||
* Added termux support.
|
||||
* Fixes to Fingers::Dirs to remove hardcoded paths. Default log path is now /tmp/fingers.log.
|
||||
* Updated Crystal version to generate clean ELF executables.
|
||||
* Improve exception handling when rendering.
|
||||
|
||||
## 2.1.1 - 16 Nov 2023
|
||||
|
||||
* Fix copy/jump when using special named capture group "match".
|
||||
|
||||
## 2.1.0 - 10 Nov 2023
|
||||
|
||||
* Added new jump functionality.
|
||||
* Improved visual feedback by discarding unreachable highlights.
|
||||
|
||||
## 2.0.6 - 26 Oct 2023
|
||||
|
||||
* Added new option @fingers-show-copied-notification ( fixes #104 ).
|
||||
* Fix problem expanding paths in Mac OS ( thanks @brttbndr ! ).
|
||||
* Fix hints using disallowed characters like "q" ( fixes #105 ).
|
||||
* Improve performance in hint generation.
|
||||
|
||||
## 2.0.5 - 06 Oct 2023
|
||||
|
||||
* Fix git/binary version mismatch again ( fixes #103 ).
|
||||
|
||||
## 2.0.4 - 05 Oct 2023
|
||||
|
||||
* Fix issues when using backquote as tmux prefix ( fixes #102 ).
|
||||
|
||||
## 2.0.3 - 29 Sep 2023
|
||||
|
||||
* Fix git/binary version mismatch by publishing a new version ( fixes #101 ).
|
||||
|
||||
## 2.0.2 - 28 Sep 2023
|
||||
|
||||
* Fix `prefix2` being lost after exiting fingers mode ( fixes #100 ).
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
FROM crystallang/crystal:latest-alpine
|
||||
RUN apk upgrade && apk add bash libevent-dev ncurses-dev ncurses hyperfine
|
||||
FROM crystallang/crystal:1.19-alpine
|
||||
ENV LANG=en_US.UTF-8
|
||||
RUN apk upgrade && apk add bash libevent-dev ncurses-dev ncurses hyperfine bison
|
||||
COPY ./spec/install-tmux-versions.sh /opt/install-tmux-versions.sh
|
||||
COPY ./spec/use-tmux.sh /opt/use-tmux.sh
|
||||
RUN bash /opt/install-tmux-versions.sh
|
||||
|
|
|
|||
7
Makefile
7
Makefile
|
|
@ -3,4 +3,9 @@ default:
|
|||
|
||||
shell: default
|
||||
docker run -it --rm -v $(shell pwd):/app fingers bash
|
||||
echo "Rebuilding tmux-fingers ..." && shards build --production
|
||||
|
||||
dev: default
|
||||
docker run -it --rm -v $(shell pwd):/app fingers bash -c "/opt/use-tmux.sh 3.6a; FINGERS_LOG_PATH='/app/fingers.log' shards build; tmux -f spec/conf/dev.conf \; new-session \; split-window 'tail -f /root/.local/state/tmux-fingers/fingers.log' \; last-pane"
|
||||
|
||||
test: default
|
||||
docker run -it --rm -v $(shell pwd):/app fingers bash -c "shards install && crystal spec"
|
||||
|
|
|
|||
183
README.md
183
README.md
|
|
@ -1,4 +1,3 @@
|
|||
|
||||

|
||||
|
||||

|
||||
|
|
@ -20,6 +19,8 @@ Here is a list of the stuff highlighted by default.
|
|||
* UUIDs
|
||||
* git status/diff output
|
||||
|
||||
Checkout [list of built-in patterns](#fingers-enabled-builtin-patterns).
|
||||
|
||||
## Key shortcuts
|
||||
|
||||
While in **[fingers]** mode, you can use the following shortcuts:
|
||||
|
|
@ -41,11 +42,15 @@ While in **[fingers]** mode, you can use the following shortcuts:
|
|||
|
||||
Add the following to your list of TPM plugins in `.tmux.conf`:
|
||||
|
||||
```
|
||||
```tmux
|
||||
set -g @plugin 'Morantron/tmux-fingers'
|
||||
```
|
||||
|
||||
Hit <kbd>prefix</kbd> + <kbd>I</kbd> to fetch and source the plugin. The first time you run it you'll be presented with a wizard to complete the installation.
|
||||
Hit <kbd>prefix</kbd> + <kbd>I</kbd> to fetch and source the plugin. The first time you run it you'll be presented with a wizard to complete the installation. Depending on the platform, the wizard will offer the following installation methods:
|
||||
|
||||
- Building from source (requires [crystal](https://crystal-lang.org/install/)). _Available in all platforms_
|
||||
- Install through [brew](https://brew.sh). _macOS only_.
|
||||
- Download standalone binary. _Linux x86_64 and macOS arm64 only_.
|
||||
|
||||
## Manual
|
||||
|
||||
|
|
@ -57,7 +62,7 @@ $ git clone https://github.com/Morantron/tmux-fingers ~/.tmux/plugins/tmux-finge
|
|||
|
||||
Source it in your `.tmux.conf`:
|
||||
|
||||
```
|
||||
```tmux
|
||||
run-shell ~/.tmux/plugins/tmux-fingers/tmux-fingers.tmux
|
||||
```
|
||||
|
||||
|
|
@ -67,14 +72,15 @@ Reload TMUX conf by running:
|
|||
$ tmux source-file ~/.tmux.conf
|
||||
```
|
||||
|
||||
The first time you run it you'll be presented with a wizard to complete the installation.
|
||||
The first time you run it you'll be presented with a wizard to complete the installation.
|
||||
|
||||
# Configuration
|
||||
|
||||
NOTE: for changes to take effect, you'll need to source again your `.tmux.conf` file.
|
||||
|
||||
* [@fingers-key](#fingers-key)
|
||||
* [@fingers-patterns-N](#fingers-patterns-N)
|
||||
* [@fingers-jump-key](#fingers-jump-key)
|
||||
* [@fingers-pattern-N](#fingers-pattern-n)
|
||||
* [@fingers-main-action](#fingers-main-action)
|
||||
* [@fingers-ctrl-action](#fingers-ctrl-action)
|
||||
* [@fingers-alt-action](#fingers-alt-action)
|
||||
|
|
@ -86,24 +92,43 @@ NOTE: for changes to take effect, you'll need to source again your `.tmux.conf`
|
|||
* [@fingers-selected-highlight-style](#fingers-selected-highlight-style)
|
||||
* [@fingers-hint-position](#fingers-hint-position)
|
||||
* [@fingers-keyboard-layout](#fingers-keyboard-layout)
|
||||
* [@fingers-show-copied-notification](#fingers-show-copied-notification)
|
||||
* [@fingers-enabled-builtin-patterns](#fingers-enabled-builtin-patterns)
|
||||
* [@fingers-use-system-clipboard](#fingers-use-system-clipboard)
|
||||
* [@fingers-enable-bindings](#fingers-enable-bindings)
|
||||
|
||||
Recipes:
|
||||
|
||||
* [Start tmux-fingers without prefix](#start-tmux-fingers-without-prefix)
|
||||
* [Using only specific patterns](#using-only-specific-patterns)
|
||||
* [Using arbitrary commands](#using-arbitrary-commands)
|
||||
* [Target adjacent panes](#target-adjacent-panes)
|
||||
|
||||
## @fingers-key
|
||||
|
||||
`default: F`
|
||||
|
||||
Customize how to enter fingers mode. Always preceded by prefix: `prefix + @fingers-key`
|
||||
Customize how to enter fingers mode. Always preceded by prefix: `prefix + @fingers-key`.
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
```tmux
|
||||
set -g @fingers-key F
|
||||
```
|
||||
|
||||
## @fingers-patterns-N
|
||||
## @fingers-jump-key
|
||||
|
||||
`default: J`
|
||||
|
||||
Customize how to enter fingers jump mode. Always preceded by prefix: `prefix + @fingers-jump-key`.
|
||||
|
||||
In jump mode, the cursor will be placed in the position of the match after the hint is selected.
|
||||
|
||||
## @fingers-pattern-N
|
||||
|
||||
You can also add additional patterns if you want more stuff to be highlighted:
|
||||
|
||||
```
|
||||
```tmux
|
||||
# You can define custom patterns like this
|
||||
set -g @fingers-pattern-0 'git rebase --(abort|continue)'
|
||||
|
||||
|
|
@ -132,7 +157,7 @@ By default **tmux-fingers** will copy matches in tmux and system clipboard.
|
|||
|
||||
If you still want to set your own custom command you can do so like this:
|
||||
|
||||
```
|
||||
```tmux
|
||||
set -g @fingers-main-action '<your command here>'
|
||||
```
|
||||
This command will also receive the following:
|
||||
|
|
@ -165,7 +190,7 @@ Same as [@fingers-main-action](#fingers-main-action) but only called when match
|
|||
|
||||
## @fingers-hint-style
|
||||
|
||||
`default: "fg=green,bold`
|
||||
`default: "fg=green,bold"`
|
||||
|
||||
With this option you can define the styles for the letter hints.
|
||||
|
||||
|
|
@ -179,13 +204,13 @@ Supported styles are: `bright`, `bold`, `dim`, `underscore`, `italics`.
|
|||
|
||||
`default: "fg=yellow"`
|
||||
|
||||
Custom styles for the highlighted match. See [@fingers-hint-format](#fingers-hint-format) for more details.
|
||||
Custom styles for the highlighted match. See [@fingers-hint-style](#fingers-hint-style) for more details.
|
||||
|
||||
## @fingers-backdrop-style
|
||||
|
||||
`default: ""`
|
||||
|
||||
Custom styles for all the text that is not matched. See [@fingers-hint-format](#fingers-hint-format) for more details.
|
||||
Custom styles for all the text that is not matched. See [@fingers-hint-style](#fingers-hint-style) for more details.
|
||||
|
||||
## @fingers-selected-hint-style
|
||||
|
||||
|
|
@ -233,6 +258,136 @@ Hints are generated taking optimal finger movement into account. You can choose
|
|||
* `dvorak-right-hand`
|
||||
* `dvorak-homerow`
|
||||
|
||||
## @fingers-show-copied-notification
|
||||
|
||||
`default: 0`
|
||||
|
||||
Show a message using `tmux display-message` notifying about the copied result.
|
||||
|
||||
## @fingers-enabled-builtin-patterns
|
||||
|
||||
`default: all`
|
||||
|
||||
A list of comma separated pattern names. Built-in patterns are the following:
|
||||
|
||||
| Name | Description | Example |
|
||||
| ----------------- | --------------------------------------------------------- | ---------------------------------------------- |
|
||||
| ip | ipv4 addresses | `192.168.0.1` |
|
||||
| uuid | uuid identifier | `f1b43afb-773c-4da2-9ae5-fef1aa6945ce` |
|
||||
| sha | sha identifier | `c8b911e2c7e9a6cc57143eaa12cad57c1f0d69df` |
|
||||
| digit | four or more digits | `1337` |
|
||||
| url | urls (supported protocols: http/https/git/ssh/file) | `https://asdf.com` |
|
||||
| path | file paths | `path/to/file` |
|
||||
| hex | hexidecimal numbers | `0x00FF` |
|
||||
| kubernetes | kubernetes identifier | `deployment.apps/zookeeper` |
|
||||
| git-status | will match file paths in the output of git status | `modified: ./path/to/file` |
|
||||
| git-status-branch | will match branch name in the output of git status | `Your branch is up to date withname-of-branch` |
|
||||
| diff | will match paths in diff output | `+++ a/path/to/file` |
|
||||
|
||||
## @fingers-use-system-clipboard
|
||||
|
||||
`default: 1`
|
||||
|
||||
Whether to use the system clipboard when copying matches. If set to `0`,
|
||||
tmux-fingers will only copy matches to the tmux buffer.
|
||||
|
||||
## @fingers-enable-bindings
|
||||
|
||||
`default: 1`
|
||||
|
||||
By default, tmux-fingers will bind to `prefix` + `@fingers-key` and `prefix` +
|
||||
`@fingers-jump-key`. You can opt-out of these bindings in case they clash with
|
||||
other parts of your configuration.
|
||||
|
||||
Check [recipes](#Recipes) for alternative ways to set up bindings.
|
||||
|
||||
## @fingers-cli
|
||||
|
||||
You can set up key bindings directly to invoke tmux-fingers by using a special global option `@fingers-cli` exposed by the plugin.
|
||||
|
||||
The following options are available:
|
||||
|
||||
```
|
||||
Usage:
|
||||
tmux-fingers start <arguments> [options]
|
||||
|
||||
Arguments:
|
||||
pane_id pane id (also accepts tmux target-pane tokens specified in tmux man pages) (required)
|
||||
|
||||
Options:
|
||||
--mode can be "jump" or "default" (default: default)
|
||||
--patterns comma separated list of pattern names
|
||||
--main-action command to which the output will be piped
|
||||
--ctrl-action command to which the output will be piped when holding CTRL key
|
||||
--alt-action command to which the output will be piped when holding ALT key
|
||||
--shift-action command to which the output will be pipedwhen holding SHIFT key
|
||||
-h, --help prints help
|
||||
```
|
||||
|
||||
Check some examples in the [Recipes](#Recipes) section below.
|
||||
|
||||
|
||||
# Recipes
|
||||
|
||||
## Start tmux-fingers without prefix
|
||||
|
||||
You can start tmux-fingers without having to press tmux prefix by adding bindings like this:
|
||||
|
||||
```tmux
|
||||
# tmux.conf
|
||||
|
||||
# Start tmux fingers by pressing Alt+F
|
||||
bind -n M-f run -b "#{@fingers-cli} start #{pane_id}"
|
||||
|
||||
# Start tmux fingers in jump mode by pressing Alt+J
|
||||
bind -n M-j run -b "#{@fingers-cli} start #{pane_id} --mode jump"
|
||||
|
||||
```
|
||||
|
||||
## Using only specific patterns
|
||||
|
||||
You can start tmux-fingers with an specific set of built-in or custom patterns.
|
||||
|
||||
```tmux
|
||||
# match urls with prefix + u
|
||||
bind u run -b "#{@fingers-cli} start #{pane_id} --patterns url"
|
||||
|
||||
# match hashes with prefix + h
|
||||
bind h run -b "#{@fingers-cli} start #{pane_id} --patterns sha"
|
||||
|
||||
# match git stuff with prefix + g
|
||||
bind g run -b "#{@fingers-cli} start #{pane_id} --patterns git-status,git-status-branch"
|
||||
|
||||
# match custom pattern with prefix + y
|
||||
set -g @fingers-pattern-yolo "yolo.*"
|
||||
bind y run -b "#{@fingers-cli} start #{pane_id} --patterns yolo"
|
||||
```
|
||||
|
||||
## Using arbitrary commands
|
||||
|
||||
You can use tmux-fingers with any arbitrary command.
|
||||
|
||||
```tmux
|
||||
# edit file using nvim in a new tmux window with prefix + e
|
||||
bind e run -b "#{@fingers-cli} start #{pane_id} --patterns path --main-action 'xargs tmux new-window nvim'"
|
||||
```
|
||||
|
||||
## Target adjacent panes
|
||||
|
||||
You can use tmux target-pane tokens to target adjacent panes. This
|
||||
configuration uses vim-style <kbd>ALT</kbd>+<kbd>hjkl</kbd> keys to
|
||||
directionally adjacent panes.
|
||||
|
||||
Also uses <kbd>ALT</kbd>+<kbd>o</kbd> to target the last pane.
|
||||
|
||||
```tmux
|
||||
bind -n M-h run -b "#{@fingers-cli} start {left-of}"
|
||||
bind -n M-j run -b "#{@fingers-cli} start {down-of}"
|
||||
bind -n M-k run -b "#{@fingers-cli} start {up-of}"
|
||||
bind -n M-l run -b "#{@fingers-cli} start {right-of}"
|
||||
bind -n M-o run -b "#{@fingers-cli} start {last}"
|
||||
```
|
||||
|
||||
# Acknowledgements and inspiration
|
||||
|
||||
This plugin is heavily inspired by
|
||||
|
|
|
|||
13
benchmark.sh
13
benchmark.sh
|
|
@ -1,13 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
/opt/use-tmux.sh 3.3a
|
||||
|
||||
shards build --production
|
||||
tmux -f /app/spec/conf/benchmark.conf new-session -d
|
||||
tmux resize-window -t '@0' -x 300 -y 300
|
||||
tmux send-keys 'COLUMNS=300 LINES=100 crystal run spec/fill_screen.cr'
|
||||
tmux send-keys Enter
|
||||
|
||||
sleep 5
|
||||
|
||||
tmux new-window 'hyperfine --warmup 5 "bin/tmux-fingers start %0 self" --export-markdown /tmp/benchmark-output'
|
||||
119
dev/benchmark.cr
Normal file
119
dev/benchmark.cr
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
require "json"
|
||||
require "tablo"
|
||||
|
||||
def fix_git_shit
|
||||
`git config --global --add safe.directory /app`
|
||||
end
|
||||
|
||||
def cleanup
|
||||
`tmux kill-server`
|
||||
`rm -rf ~/.local/state/tmux-fingers`
|
||||
end
|
||||
|
||||
def run_benchmark
|
||||
runs = ENV["BENCHMARK_RUNS"]? || "100"
|
||||
|
||||
`FINGERS_LOG_PATH=fingers.log shards build --production`
|
||||
|
||||
puts "running: tmux -f #{Dir.current}/spec/conf/benchmark.conf new-session -d"
|
||||
`tmux -f #{Dir.current}/spec/conf/benchmark.conf new-session -d`
|
||||
`tmux resize-window -t '@0' -x 300 -y 300`
|
||||
`tmux send-keys 'COLUMNS=300 LINES=100 crystal run spec/fill_screen.cr'`
|
||||
`tmux send-keys Enter`
|
||||
|
||||
sleep 5
|
||||
|
||||
puts "Running benchmarks with #{runs} runs..."
|
||||
|
||||
output_file = File.tempfile("benchmark")
|
||||
|
||||
`tmux new-window 'hyperfine --prepare "bash kill-windows.sh" --warmup 5 --runs #{runs} "bin/tmux-fingers start %0" --export-json #{output_file.path}'`
|
||||
|
||||
while File.size(output_file.path) == 0
|
||||
puts "Waiting for benchmark results"
|
||||
sleep 5
|
||||
end
|
||||
|
||||
cleanup
|
||||
|
||||
JSON.parse(output_file)
|
||||
end
|
||||
|
||||
def replace_string_in_file(file_path : String, search_string : String, replace_string : String)
|
||||
content = File.read(file_path)
|
||||
|
||||
updated_content = content.gsub(search_string, replace_string)
|
||||
|
||||
File.write(file_path, updated_content)
|
||||
end
|
||||
|
||||
def clone_repo_and_cd(version)
|
||||
fix_git_shit
|
||||
|
||||
repo_path = "/tmp/tmux-fingers-#{version}"
|
||||
`git clone /app #{repo_path}`
|
||||
|
||||
Dir.cd(repo_path)
|
||||
|
||||
`git checkout #{version}`
|
||||
|
||||
replace_string_in_file(
|
||||
"#{repo_path}/spec/conf/benchmark.conf",
|
||||
"/app/tmux-fingers.tmux",
|
||||
"#{repo_path}/tmux-fingers.tmux"
|
||||
)
|
||||
end
|
||||
|
||||
versions = ARGV
|
||||
|
||||
results = [] of JSON::Any
|
||||
|
||||
versions.each do |version|
|
||||
clone_repo_and_cd(version)
|
||||
|
||||
results << run_benchmark
|
||||
end
|
||||
|
||||
data = versions.map_with_index do |version, index|
|
||||
result = results[index]?
|
||||
next unless result
|
||||
|
||||
[
|
||||
version,
|
||||
result["results"][0]["mean"].as_f,
|
||||
result["results"][0]["stddev"].as_f,
|
||||
result["results"][0]["min"].as_f,
|
||||
result["results"][0]["max"].as_f,
|
||||
]
|
||||
end.compact
|
||||
|
||||
time_format = "%.6fs"
|
||||
|
||||
table = Tablo::Table.new(data, wrap_body_cells_to: nil) do |t|
|
||||
t.add_column("Version", width: 40) { |row| row[0] }
|
||||
t.add_column("Mean", width: 14) { |row| time_format % row[1] }
|
||||
t.add_column("Std Dev", width: 14) { |row| time_format % row[2] }
|
||||
t.add_column("Min", width: 14) { |row| time_format % row[3] }
|
||||
t.add_column("Max", width: 14) { |row| time_format % row[4] }
|
||||
end
|
||||
|
||||
puts table
|
||||
|
||||
# The baseline is the last version passed
|
||||
threshold = (ENV["BENCHMARK_THRESHOLD"]? || "10").to_f / 100.0
|
||||
baseline_mean = data.last[1].as(Float64)
|
||||
|
||||
regression_found = false
|
||||
|
||||
data[0..-2].each do |row|
|
||||
version = row[0]
|
||||
mean = row[1].as(Float64)
|
||||
diff = (mean - baseline_mean) / baseline_mean
|
||||
|
||||
if diff > threshold
|
||||
puts "\nRegression detected: #{version} is #{"%.1f" % (diff * 100)}% slower than baseline #{versions.last} (threshold: #{"%.1f" % (threshold * 100)}%)"
|
||||
regression_found = true
|
||||
end
|
||||
end
|
||||
|
||||
exit 1 if regression_found
|
||||
77
dev/release.cr
Normal file
77
dev/release.cr
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
require "semantic_version"
|
||||
require "yaml"
|
||||
|
||||
struct ShardFile
|
||||
include YAML::Serializable
|
||||
include YAML::Serializable::Unmapped
|
||||
|
||||
property version : String
|
||||
end
|
||||
|
||||
current_version = SemanticVersion.parse(`shards version`.chomp)
|
||||
current_branch = `git symbolic-ref --short HEAD`.chomp
|
||||
pending_changes = `git status -s`.chomp
|
||||
|
||||
if current_branch != "develop"
|
||||
puts "This script should be ran from develop branch"
|
||||
exit 1
|
||||
end
|
||||
|
||||
if pending_changes != ""
|
||||
puts "There are uncommited changes"
|
||||
exit 1
|
||||
end
|
||||
|
||||
puts "Which component you want to bump? major.minor.patch"
|
||||
print "> "
|
||||
|
||||
component = gets
|
||||
|
||||
puts "Bumping #{component} in #{current_version}"
|
||||
|
||||
next_version = case component
|
||||
when "major"
|
||||
current_version.bump_major
|
||||
when "minor"
|
||||
current_version.bump_minor
|
||||
when "patch"
|
||||
current_version.bump_patch
|
||||
else
|
||||
current_version.bump_patch
|
||||
end
|
||||
|
||||
shard = ShardFile.from_yaml(File.read("shard.yml"))
|
||||
shard.version = next_version.to_s
|
||||
|
||||
File.write("shard.yml", shard.to_yaml)
|
||||
|
||||
current_date = Time.local.to_s("%d %b %Y")
|
||||
|
||||
`git add shard.yml`
|
||||
`git commit -am "bump version in shard.yml"`
|
||||
|
||||
content_to_prepend = "## #{next_version.to_s} - #{current_date}\n\nEDIT THIS:\n\n#{`git log --oneline #{current_version}..@`.chomp}\n\n"
|
||||
|
||||
original_content = File.read("CHANGELOG.md")
|
||||
File.write("CHANGELOG.md", content_to_prepend + original_content)
|
||||
|
||||
Process.run(ENV["EDITOR"], args: ["CHANGELOG.md"], input: :inherit, output: :inherit, error: :inherit)
|
||||
|
||||
`git add CHANGELOG.md`
|
||||
`git commit -am 'updated CHANGELOG.md'`
|
||||
|
||||
print "Confirm release? [Y/n]\n >"
|
||||
answer = gets
|
||||
|
||||
if answer == "n"
|
||||
puts "Canceling release"
|
||||
exit 1
|
||||
end
|
||||
|
||||
`git checkout master`
|
||||
`git merge develop`
|
||||
`git tag #{next_version.to_s}`
|
||||
|
||||
puts "Run the following command to push the release"
|
||||
puts ""
|
||||
puts "git push && git push --tags"
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
# Health check troubleshooting
|
||||
|
||||
`health-check.sh` performs a checks to ensure that your system has all dependencies needed to run smoothly.
|
||||
|
||||
They are not much, and in most GNU/linux environments these are the defaults anyway.
|
||||
|
||||
To run the check you need to run the following command.
|
||||
|
||||
`$ /path/to/tmux-fingers/scripts/health-check.sh`
|
||||
|
||||
## gawk not found
|
||||
|
||||
Install `gawk` package and reload your tmux.conf.
|
||||
|
||||
### OSX
|
||||
|
||||
```
|
||||
$ brew install gawk && tmux source ~/.tmux.conf
|
||||
```
|
||||
|
||||
### Linux
|
||||
|
||||
* Ubuntu: `$ sudo aptitude install gawk && tmux source ~/.tmux.conf`
|
||||
* Arch linux: `$ sudo pacman -S install gawk && tmux source ~/.tmux.conf`
|
||||
|
||||
## bash version is too old
|
||||
|
||||
This probably means you are running OSX, which ships with *bash 3*. In order to upgrade to *bash 4* you need need to run:
|
||||
|
||||
`$ brew install bash`
|
||||
|
||||
## tmux version is too old
|
||||
|
||||
You can install latest *tmux* from source, check https://github.com/tmux/tmux
|
||||
|
||||
## Wrong $TERM value
|
||||
|
||||
Tmux fingers works better with proper 256 support. Set `@default-terminal` to either `screen-256color` or `xterm-256color`.
|
||||
|
||||
```
|
||||
set -g @default-terminal "screen-256color"
|
||||
```
|
||||
|
||||
## submodules not initialized properly
|
||||
|
||||
This could happen after an update from a tmux-fingers version prior to 0.6.x,
|
||||
ensure that all tmux-fingers dependencies are installed properly by running the
|
||||
following:
|
||||
|
||||
```
|
||||
cd ~/.tmux/plugins/tmux-fingers
|
||||
git submodule update --init --recursive"
|
||||
tmux source ~/.tmux.conf"
|
||||
```
|
||||
|
||||
## system clipboard integration
|
||||
|
||||
In order for system clipboard integration to work, you need to have program in your system that manages that.
|
||||
|
||||
Check [tmux-yank documentation](https://github.com/tmux-plugins/tmux-yank#requirements) to check what program do you need to install, depending on your platform.
|
||||
|
|
@ -4,7 +4,6 @@ CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|||
PLATFORM=$(uname -s)
|
||||
action=$1
|
||||
|
||||
|
||||
# set up exit trap
|
||||
function finish {
|
||||
exit_code=$?
|
||||
|
|
@ -32,14 +31,26 @@ trap finish EXIT
|
|||
function install_from_source() {
|
||||
echo "Installing from source..."
|
||||
|
||||
# check if crystal is installed
|
||||
if ! command -v crystal >/dev/null 2>&1; then
|
||||
echo "crystal is not installed. Please install it first."
|
||||
echo ""
|
||||
echo " https://crystal-lang.org/install/"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# check if shards is installed
|
||||
if ! command -v shards >/dev/null 2>&1; then
|
||||
echo "crystal is not installed. Please install it first."
|
||||
echo "shards is not installed. Please install it first."
|
||||
echo ""
|
||||
echo " https://crystal-lang.org/reference/latest/man/shards/index.html"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pushd $CURRENT_DIR > /dev/null
|
||||
shards build --production
|
||||
WIZARD_INSTALLATION_METHOD=build-from-source shards build --production
|
||||
popd > /dev/null
|
||||
|
||||
echo "Build complete!"
|
||||
|
|
@ -56,18 +67,37 @@ function install_with_brew() {
|
|||
}
|
||||
|
||||
|
||||
function asset_suffix() {
|
||||
local arch=$(uname -m)
|
||||
|
||||
if [[ "$PLATFORM" == "Linux" && "$arch" == "x86_64" ]]; then
|
||||
echo "linux-x86_64"
|
||||
elif [[ "$PLATFORM" == "Darwin" && "$arch" == "arm64" ]]; then
|
||||
echo "macos-arm64"
|
||||
fi
|
||||
}
|
||||
|
||||
function download_binary() {
|
||||
local suffix=$(asset_suffix)
|
||||
|
||||
if [[ -z "$suffix" ]]; then
|
||||
echo "No pre-built binary available for your platform ($(uname -s) $(uname -m))."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p $CURRENT_DIR/bin
|
||||
# TODO check architecture
|
||||
|
||||
echo "Getting latest release..."
|
||||
|
||||
# TODO use "latest" tag
|
||||
url=$(curl -s "https://api.github.com/repos/morantron/tmux-fingers/releases" | grep browser_download_url | head -1 | grep -o https://.*x86_64)
|
||||
url=$(curl -s "https://api.github.com/repos/Morantron/tmux-fingers/releases" | grep -o "https://.*${suffix}" | head -1)
|
||||
|
||||
if [[ -z "$url" ]]; then
|
||||
echo "Could not find a release for tmux-fingers. Please try again later."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Downloading binary from $url"
|
||||
|
||||
# download binary to bin/tmux-fingers
|
||||
curl -L $url -o $CURRENT_DIR/bin/tmux-fingers
|
||||
chmod a+x $CURRENT_DIR/bin/tmux-fingers
|
||||
|
||||
|
|
@ -89,40 +119,37 @@ if [[ "$1" == "install-from-source" ]]; then
|
|||
install_from_source
|
||||
fi
|
||||
|
||||
function binary_or_brew_label() {
|
||||
if [[ "$PLATFORM" == "Darwin" ]]; then
|
||||
echo "Install with brew"
|
||||
else
|
||||
echo "Download binary"
|
||||
fi
|
||||
}
|
||||
|
||||
function binary_or_brew_action() {
|
||||
if [[ "$PLATFORM" == "Darwin" ]]; then
|
||||
echo "install-with-brew"
|
||||
else
|
||||
echo "download-binary"
|
||||
fi
|
||||
}
|
||||
|
||||
function get_message() {
|
||||
if [[ "$FINGERS_UPDATE" == "1" ]]; then
|
||||
echo "It looks like tmux-fingers has been updated. We need to rebuild the binary."
|
||||
else
|
||||
echo "It looks like it is the first time you are running the plugin. We first need to get tmux-fingers binary for things to work."
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
tmux display-menu -T "tmux-fingers" \
|
||||
"" \
|
||||
"- " "" ""\
|
||||
"- #[nodim,bold]Welcome to tmux-fingers! ✌️ " "" ""\
|
||||
"- " "" ""\
|
||||
"- $(get_message) " "" "" \
|
||||
"- " "" ""\
|
||||
"" \
|
||||
"$(binary_or_brew_label)" b "new-window \"$CURRENT_DIR/install-wizard.sh $(binary_or_brew_action)\"" \
|
||||
"Build from source" s "new-window \"$CURRENT_DIR/install-wizard.sh install-from-source\"" \
|
||||
"" \
|
||||
menu_args=(
|
||||
-T "tmux-fingers"
|
||||
""
|
||||
"- " "" ""
|
||||
"- #[nodim,bold]Welcome to tmux-fingers! ✌️ " "" ""
|
||||
"- " "" ""
|
||||
"- $(get_message) " "" ""
|
||||
"- " "" ""
|
||||
""
|
||||
)
|
||||
|
||||
if [[ -n "$(asset_suffix)" ]]; then
|
||||
menu_args+=("Download binary" b "new-window \"$CURRENT_DIR/install-wizard.sh download-binary\"")
|
||||
fi
|
||||
|
||||
if [[ "$PLATFORM" == "Darwin" ]]; then
|
||||
menu_args+=("Install with brew" w "new-window \"$CURRENT_DIR/install-wizard.sh install-with-brew\"")
|
||||
fi
|
||||
|
||||
menu_args+=(
|
||||
"Build from source (crystal required)" s "new-window \"$CURRENT_DIR/install-wizard.sh install-from-source\""
|
||||
""
|
||||
"Exit" q ""
|
||||
)
|
||||
|
||||
tmux display-menu "${menu_args[@]}"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
for id in $(tmux list-windows -F "#{window_id}:#{pane_id}:#{window_name}" | grep -v ":$1:" | grep "fingers" | cut -f1 -d:); do
|
||||
for id in $(tmux list-windows -F "#{window_id}:#{pane_id}:#{window_name}" | grep -v ":%0:" | grep "fingers" | cut -f1 -d:); do
|
||||
tmux kill-window -t $id
|
||||
done
|
||||
|
|
|
|||
14
shard.lock
14
shard.lock
|
|
@ -1,6 +1,14 @@
|
|||
version: 2.0
|
||||
shards:
|
||||
ameba:
|
||||
git: https://github.com/crystal-ameba/ameba.git
|
||||
version: 1.3.1
|
||||
cling:
|
||||
git: https://github.com/devnote-dev/cling.git
|
||||
version: 3.0.0
|
||||
|
||||
tablo:
|
||||
git: https://github.com/hutou/tablo.git
|
||||
version: 0.10.1
|
||||
|
||||
xdg_base_directory:
|
||||
git: https://github.com/tijn/xdg_base_directory.git
|
||||
version: 1.0.5+git.commit.60bf28dc060c221d5af52727927e92b840022eb0
|
||||
|
||||
|
|
|
|||
24
shard.yml
24
shard.yml
|
|
@ -1,18 +1,18 @@
|
|||
---
|
||||
version: 2.7.1
|
||||
name: fingers
|
||||
version: 2.0.1
|
||||
|
||||
authors:
|
||||
- Jorge Morante <jorge@morante.eu>
|
||||
|
||||
- Jorge Morante <jorge@morante.eu>
|
||||
targets:
|
||||
tmux-fingers:
|
||||
main: src/fingers.cr
|
||||
|
||||
development_dependencies:
|
||||
ameba:
|
||||
github: crystal-ameba/ameba
|
||||
version: ~> 1.3.0
|
||||
|
||||
crystal: 1.9.2
|
||||
|
||||
dependencies:
|
||||
cling:
|
||||
github: devnote-dev/cling
|
||||
version: '>= 3.0.0'
|
||||
xdg_base_directory:
|
||||
github: tijn/xdg_base_directory
|
||||
tablo:
|
||||
github: hutou/tablo
|
||||
crystal: 1.19
|
||||
license: MIT
|
||||
|
|
|
|||
7
spec/conf/dev.conf
Normal file
7
spec/conf/dev.conf
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
set -g default-terminal 'screen-256color'
|
||||
set -g @fingers-skip-wizard '1'
|
||||
|
||||
bind C-r source-file /app/spec/conf/dev.conf
|
||||
bind Enter run "cd /app && FINGERS_LOG_PATH='/app/fingers.log' shards build --production"
|
||||
|
||||
run '/app/tmux-fingers.tmux'
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
if [[ -n "$CI_TMUX_VERSION" ]]; then
|
||||
VERSIONS=("$CI_TMUX_VERSION")
|
||||
else
|
||||
VERSIONS=("3.0a" "3.1c" "3.2a" "3.3a")
|
||||
VERSIONS=("3.0a" "3.1c" "3.2a" "3.3a" "3.4" "3.5a" "3.6a")
|
||||
fi
|
||||
|
||||
mkdir -p /opt
|
||||
chmod a+w /opt
|
||||
|
||||
pushd /tmp
|
||||
for version in "${VERSIONS[@]}";
|
||||
|
|
@ -22,6 +22,7 @@ pushd /tmp
|
|||
pushd "/opt/tmux-${version}"
|
||||
./configure
|
||||
make
|
||||
chmod -R a+r /opt/tmux-${version}
|
||||
popd
|
||||
done
|
||||
popd
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ describe Fingers::Hinter do
|
|||
input = generate_lines
|
||||
output = TextOutput.new
|
||||
|
||||
patterns = Fingers::Config::DEFAULT_PATTERNS.values.to_a
|
||||
patterns = Fingers::BUILTIN_PATTERNS.values.to_a
|
||||
alphabet = "asdf".split("")
|
||||
|
||||
hinter = Fingers::Hinter.new(
|
||||
|
|
@ -46,13 +46,6 @@ describe Fingers::Hinter do
|
|||
alphabet: alphabet,
|
||||
output: output,
|
||||
)
|
||||
|
||||
puts "before"
|
||||
puts input
|
||||
puts "---------"
|
||||
puts "after"
|
||||
hinter.run
|
||||
puts output.contents
|
||||
end
|
||||
|
||||
it "only highlights captured groups" do
|
||||
|
|
@ -77,7 +70,7 @@ Changes not staged for commit:
|
|||
"
|
||||
output = TextOutput.new
|
||||
|
||||
patterns = Fingers::Config::DEFAULT_PATTERNS.values.to_a
|
||||
patterns = Fingers::BUILTIN_PATTERNS.values.to_a
|
||||
patterns << "On branch (?<capture>.*)"
|
||||
alphabet = "asdf".split("")
|
||||
|
||||
|
|
@ -89,12 +82,58 @@ Changes not staged for commit:
|
|||
alphabet: alphabet,
|
||||
output: output,
|
||||
)
|
||||
end
|
||||
|
||||
it "only reuses hints when allow duplicates is false" do
|
||||
width = 100
|
||||
output = TextOutput.new
|
||||
|
||||
patterns = Fingers::BUILTIN_PATTERNS.values.to_a
|
||||
alphabet = "asdf".split("")
|
||||
|
||||
input = "
|
||||
modified: src/fingers/cli.cr
|
||||
modified: src/fingers/cli.cr
|
||||
modified: src/fingers/cli.cr
|
||||
"
|
||||
|
||||
hinter = Fingers::Hinter.new(
|
||||
input: input.split("\n"),
|
||||
width: width,
|
||||
patterns: patterns,
|
||||
state: ::Fingers::State.new,
|
||||
alphabet: alphabet,
|
||||
output: output,
|
||||
reuse_hints: false
|
||||
)
|
||||
|
||||
puts "before"
|
||||
puts input
|
||||
puts "---------"
|
||||
puts "after"
|
||||
hinter.run
|
||||
puts output.contents
|
||||
end
|
||||
|
||||
it "can rerender when not reusing hints" do
|
||||
width = 100
|
||||
output = TextOutput.new
|
||||
|
||||
patterns = Fingers::BUILTIN_PATTERNS.values.to_a
|
||||
alphabet = "asdf".split("")
|
||||
|
||||
input = "
|
||||
modified: src/fingers/cli.cr
|
||||
modified: src/fingers/cli.cr
|
||||
modified: src/fingers/cli.cr
|
||||
"
|
||||
|
||||
hinter = Fingers::Hinter.new(
|
||||
input: input.split("\n"),
|
||||
width: width,
|
||||
patterns: patterns,
|
||||
state: ::Fingers::State.new,
|
||||
alphabet: alphabet,
|
||||
output: output,
|
||||
reuse_hints: false
|
||||
)
|
||||
|
||||
hinter.run
|
||||
hinter.run
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ require "../../../src/fingers/dirs"
|
|||
require "../../../src/fingers/match_formatter"
|
||||
|
||||
def setup(
|
||||
hint_style : String = "#[fg=yellow,bold]",
|
||||
highlight_style : String = "#[fg=yellow]",
|
||||
hint_style : String = "#[hint]",
|
||||
highlight_style : String = "#[highlight]",
|
||||
hint_position : String = "left",
|
||||
selected_hint_style : String = "#[fg=green,bold]",
|
||||
selected_highlight_style : String = "#[fg=green]",
|
||||
selected_hint_style : String = "#[selected_hint]",
|
||||
selected_highlight_style : String = "#[selected_highlight]",
|
||||
selected : Bool = false,
|
||||
offset : Tuple(Int32, Int32) | Nil = nil,
|
||||
hint : String = "a",
|
||||
|
|
@ -15,7 +15,7 @@ def setup(
|
|||
)
|
||||
formatter = Fingers::MatchFormatter.new(
|
||||
highlight_style: highlight_style,
|
||||
backdrop_style: "#[bg=black,fg=white]",
|
||||
backdrop_style: "#[backdrop]",
|
||||
hint_style: hint_style,
|
||||
selected_highlight_style: selected_highlight_style,
|
||||
selected_hint_style: selected_hint_style,
|
||||
|
|
@ -31,14 +31,14 @@ describe Fingers::MatchFormatter do
|
|||
context "is set to left" do
|
||||
it "places the hint on the left side" do
|
||||
result = setup(hint_position: "left")
|
||||
result.should eq("#[reset]#[fg=yellow,bold]a#[reset]#[fg=yellow]olo#[reset]#[bg=black,fg=white]")
|
||||
result.should eq("#[reset]#[reset]#[hint]a#[reset]#[highlight]olo#[reset]#[backdrop]")
|
||||
end
|
||||
end
|
||||
|
||||
context "is set to right" do
|
||||
it "places the hint on the right side" do
|
||||
result = setup(hint_position: "right")
|
||||
result.should eq("#[reset]#[fg=yellow]yol#[reset]#[fg=yellow,bold]a#[reset]#[bg=black,fg=white]")
|
||||
result.should eq("#[reset]#[reset]#[highlight]yol#[reset]#[hint]a#[reset]#[backdrop]")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -46,14 +46,21 @@ describe Fingers::MatchFormatter do
|
|||
context "when a hint is selected" do
|
||||
it "selects the correct format" do
|
||||
result = setup(selected: true)
|
||||
result.should eq("#[reset]#[fg=green,bold]a#[reset]#[fg=green]olo#[reset]#[bg=black,fg=white]")
|
||||
result.should eq("#[reset]#[reset]#[selected_hint]a#[reset]#[selected_highlight]olo#[reset]#[backdrop]")
|
||||
end
|
||||
end
|
||||
|
||||
context "when offset is provided" do
|
||||
it "only highlights at specified offset" do
|
||||
result = setup(offset: {1, 5}, highlight: "yoloyoloyolo", hint: "a")
|
||||
result.should eq("#[reset]#[bg=black,fg=white]y#[fg=yellow,bold]a#[reset]#[fg=yellow]loyo#[reset]#[bg=black,fg=white]loyolo#[bg=black,fg=white]")
|
||||
result.should eq("#[reset]#[backdrop]y#[reset]#[hint]a#[reset]#[highlight]loyo#[reset]#[backdrop]loyolo#[backdrop]")
|
||||
end
|
||||
end
|
||||
|
||||
context "when offset is at the beginning" do
|
||||
it "only highlights at specified offset" do
|
||||
result = setup(offset: {0, 3}, highlight: "yolo", hint: "a")
|
||||
result.should eq("#[reset]#[reset]#[hint]a#[reset]#[highlight]ol#[reset]#[backdrop]o#[backdrop]")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ require "../../src/fingers/config"
|
|||
require "string_scanner"
|
||||
|
||||
def matches_for(pattern_name, input)
|
||||
pattern = Regex.new(::Fingers::Config::DEFAULT_PATTERNS[pattern_name])
|
||||
pattern = Regex.new(::Fingers::BUILTIN_PATTERNS[pattern_name])
|
||||
input.scan(pattern).map { |m| m["match"]? || m[0] }
|
||||
end
|
||||
|
||||
describe "default patterns" do
|
||||
describe "builtin patterns" do
|
||||
describe "ip" do
|
||||
it "should match ip addresses" do
|
||||
input = "
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe TmuxFormatPrinter do
|
||||
let(:printer) do
|
||||
class FakeShell
|
||||
def exec(cmd)
|
||||
"$(#{cmd})"
|
||||
end
|
||||
end
|
||||
|
||||
TmuxFormatPrinter.new(shell: FakeShell.new)
|
||||
end
|
||||
|
||||
it 'transforms tmux status line format into escape sequences' do
|
||||
result = printer.print('bg=red,fg=yellow,bold', reset_styles_after: true)
|
||||
expected = '$(tput setab 1)$(tput setaf 3)$(tput bold)$(tput sgr0)'
|
||||
|
||||
expect(result).to eq(expected)
|
||||
end
|
||||
|
||||
it 'transforms tmux status line format into escape sequences' do
|
||||
result = printer.print('bg=red,fg=yellow,bold', reset_styles_after: true)
|
||||
expected = '$(tput setab 1)$(tput setaf 3)$(tput bold)$(tput sgr0)'
|
||||
|
||||
expect(result).to eq(expected)
|
||||
end
|
||||
|
||||
xit 'raises on unknown formats' do
|
||||
# TODO
|
||||
end
|
||||
end
|
||||
|
|
@ -1,25 +1,11 @@
|
|||
require "spec"
|
||||
require "../../src/tmux_style_printer"
|
||||
|
||||
class FakeShell < TmuxStylePrinter::Shell
|
||||
def exec(cmd)
|
||||
"$(#{cmd})"
|
||||
end
|
||||
end
|
||||
|
||||
describe TmuxStylePrinter do
|
||||
it "transforms tmux status line format into escape sequences" do
|
||||
printer = TmuxStylePrinter.new(shell = FakeShell.new)
|
||||
printer = TmuxStylePrinter.new
|
||||
result = printer.print("bg=red,fg=yellow,bold", reset_styles_after: true)
|
||||
expected = "$(tput setab 1)$(tput setaf 3)$(tput bold)$(tput sgr0)"
|
||||
|
||||
result.should eq expected
|
||||
end
|
||||
|
||||
it "transforms tmux status line format into escape sequences" do
|
||||
printer = TmuxStylePrinter.new(shell = FakeShell.new)
|
||||
result = printer.print("bg=red,fg=yellow,bold", reset_styles_after: true)
|
||||
expected = "$(tput setab 1)$(tput setaf 3)$(tput bold)$(tput sgr0)"
|
||||
expected = "\e[48;5;1m\e[38;5;3m\e[1m\e[0m"
|
||||
|
||||
result.should eq expected
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
require "./fingers/logger"
|
||||
require "./fingers/constants"
|
||||
require "./fingers/options"
|
||||
require "./fingers/cli"
|
||||
|
||||
def running_in_specs? : Bool
|
||||
{{ @type.has_constant?("Spec") }}
|
||||
end
|
||||
|
||||
module Fingers
|
||||
VERSION = {{ %(#{`shards version`.chomp}) }}
|
||||
|
||||
cli = Cli.new
|
||||
cli.run
|
||||
cli.run unless running_in_specs?
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,13 +4,24 @@ module Fingers
|
|||
class ActionRunner
|
||||
@final_shell_command : String | Nil
|
||||
|
||||
def initialize(@modifier : String, @match : String, @hint : String, @original_pane : Tmux::Pane)
|
||||
def initialize(
|
||||
@modifier : String,
|
||||
@match : String,
|
||||
@hint : String,
|
||||
@original_pane : Tmux::Pane,
|
||||
@offset : Tuple(Int32, Int32) | Nil,
|
||||
@mode : String,
|
||||
@main_action : String | Nil,
|
||||
@ctrl_action : String | Nil,
|
||||
@alt_action : String | Nil,
|
||||
@shift_action : String | Nil,
|
||||
)
|
||||
end
|
||||
|
||||
def run
|
||||
tmux.set_buffer(match)
|
||||
tmux.set_buffer(match, Fingers.config.use_system_clipboard)
|
||||
|
||||
return if final_shell_command.nil?
|
||||
return if final_shell_command.nil? || final_shell_command.not_nil!.empty?
|
||||
|
||||
cmd_path, *args = Process.parse_arguments(final_shell_command.not_nil!)
|
||||
|
||||
|
|
@ -19,8 +30,8 @@ module Fingers
|
|||
args,
|
||||
input: :pipe,
|
||||
output: :pipe,
|
||||
error: File.open("/tmp/action-stderr", "a"),
|
||||
chdir: original_pane.pane_current_path,
|
||||
error: File.open(::Fingers::Dirs::ROOT / "action-stderr", "a"),
|
||||
chdir: original_pane.pane_current_path.presence,
|
||||
env: action_env
|
||||
)
|
||||
|
||||
|
|
@ -28,23 +39,28 @@ module Fingers
|
|||
cmd.input.flush
|
||||
end
|
||||
|
||||
private getter :match, :modifier, :hint, :original_pane
|
||||
private getter :match, :modifier, :hint, :original_pane, :offset, :mode, :main_action, :ctrl_action, :alt_action, :shift_action
|
||||
|
||||
def final_shell_command
|
||||
return jump if mode == "jump"
|
||||
return @final_shell_command if @final_shell_command
|
||||
|
||||
@final_shell_command = case action
|
||||
when ":copy:"
|
||||
copy
|
||||
when ":open:"
|
||||
open
|
||||
when ":paste:"
|
||||
paste
|
||||
when nil
|
||||
# do nothing
|
||||
else
|
||||
shell_action
|
||||
end
|
||||
@final_shell_command = action_command
|
||||
end
|
||||
|
||||
private def action_command
|
||||
case action
|
||||
when ":copy:"
|
||||
copy
|
||||
when ":open:"
|
||||
open
|
||||
when ":paste:"
|
||||
paste
|
||||
when nil
|
||||
# do nothing
|
||||
else
|
||||
shell_action
|
||||
end
|
||||
end
|
||||
|
||||
def copy
|
||||
|
|
@ -59,8 +75,23 @@ module Fingers
|
|||
system_open_command
|
||||
end
|
||||
|
||||
def jump
|
||||
return nil if offset.nil?
|
||||
|
||||
`tmux copy-mode -t #{original_pane.pane_id}`
|
||||
`tmux send-keys -t #{original_pane.pane_id} -X top-line`
|
||||
`tmux send-keys -t #{original_pane.pane_id} -N #{offset.not_nil![0]} -X cursor-down`
|
||||
`tmux send-keys -t #{original_pane.pane_id} -N #{offset.not_nil![1]} -X cursor-right`
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def paste
|
||||
"tmux paste-buffer"
|
||||
if original_pane.pane_in_mode
|
||||
"tmux send-keys -t #{original_pane.pane_id} -X cancel \; paste-buffer -t #{original_pane.pane_id}"
|
||||
else
|
||||
"tmux paste-buffer -t #{original_pane.pane_id}"
|
||||
end
|
||||
end
|
||||
|
||||
def shell_action
|
||||
|
|
@ -74,17 +105,19 @@ module Fingers
|
|||
private property action : String | Nil do
|
||||
case modifier
|
||||
when "main"
|
||||
Fingers.config.main_action
|
||||
main_action || Fingers.config.main_action
|
||||
when "shift"
|
||||
Fingers.config.shift_action
|
||||
shift_action || Fingers.config.shift_action
|
||||
when "alt"
|
||||
Fingers.config.alt_action
|
||||
alt_action || Fingers.config.alt_action
|
||||
when "ctrl"
|
||||
Fingers.config.ctrl_action
|
||||
ctrl_action || Fingers.config.ctrl_action
|
||||
end
|
||||
end
|
||||
|
||||
def system_copy_command
|
||||
return nil unless Fingers.config.use_system_clipboard
|
||||
|
||||
@system_copy_command ||= if program_exists?("pbcopy")
|
||||
if program_exists?("reattach-to-user-namespace")
|
||||
"reattach-to-user-namespace"
|
||||
|
|
@ -92,7 +125,7 @@ module Fingers
|
|||
"pbcopy"
|
||||
end
|
||||
elsif program_exists?("clip.exe")
|
||||
"cat | clip.exe"
|
||||
"clip.exe"
|
||||
elsif program_exists?("wl-copy")
|
||||
"wl-copy"
|
||||
elsif program_exists?("xclip")
|
||||
|
|
@ -124,9 +157,14 @@ module Fingers
|
|||
|
||||
# This takes care of some path expansion weirdness when opening paths that start with ~ in MacOS
|
||||
def expanded_match
|
||||
return match unless action == ":open:"
|
||||
return match unless should_expand_match?
|
||||
|
||||
Path[match].expand(base: original_pane.pane_current_path, home: Path.home)
|
||||
end
|
||||
|
||||
private def should_expand_match?
|
||||
action == ":open:" && match.starts_with?("~")
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,24 +1,33 @@
|
|||
require "cling"
|
||||
require "./commands/*"
|
||||
|
||||
module Fingers
|
||||
class MainCommand < Cling::Command
|
||||
def setup : Nil
|
||||
@description = "description"
|
||||
@name = "tmux-fingers"
|
||||
add_command Fingers::Commands::Version.new
|
||||
add_command Fingers::Commands::LoadConfig.new
|
||||
add_command Fingers::Commands::SendInput.new
|
||||
add_command Fingers::Commands::Start.new
|
||||
add_command Fingers::Commands::Info.new
|
||||
end
|
||||
|
||||
def run(arguments, options) : Nil
|
||||
puts help_template
|
||||
end
|
||||
end
|
||||
|
||||
class Cli
|
||||
def run
|
||||
return if ARGV.size == 0
|
||||
main = MainCommand.new
|
||||
|
||||
command, *args = ARGV
|
||||
|
||||
cmd = case command
|
||||
when "start"
|
||||
Fingers::Commands::Start.new(args)
|
||||
when "load-config"
|
||||
Fingers::Commands::LoadConfig.new(args)
|
||||
when "send-input"
|
||||
Fingers::Commands::SendInput.new(args)
|
||||
when "version"
|
||||
Fingers::Commands::Version.new(args)
|
||||
end
|
||||
|
||||
cmd.run if cmd
|
||||
main.execute ARGV
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# fingers load-config
|
||||
# fingers version
|
||||
# fingers send-input INPUT
|
||||
# fingers start --mode default|jump --pane #{pane_id}
|
||||
|
|
|
|||
32
src/fingers/commands/info.cr
Normal file
32
src/fingers/commands/info.cr
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
require "cling"
|
||||
require "tablo"
|
||||
|
||||
class Fingers::Commands::Info < Cling::Command
|
||||
WIZARD_INSTALLATION_METHOD = {{ env("WIZARD_INSTALLATION_METHOD") }}
|
||||
|
||||
def setup : Nil
|
||||
@name = "info"
|
||||
end
|
||||
|
||||
def run(arguments, options) : Nil
|
||||
data = [
|
||||
["tmux-fingers", "#{Fingers::VERSION}"],
|
||||
["xdg-root-folder", "#{Fingers::Dirs::ROOT}"],
|
||||
["log-path", "#{Fingers::Dirs::LOG_PATH}"],
|
||||
["installation-method", "#{WIZARD_INSTALLATION_METHOD || "manual"}"],
|
||||
["tmux-version", `tmux -V`.chomp],
|
||||
["TERM", ENV["TERM"]? || "not set"],
|
||||
["SHELL", ENV["SHELL"]? || "not set"],
|
||||
["crystal-version", Crystal::VERSION]
|
||||
]
|
||||
|
||||
opt_width = data.map { |n| n[0].size }.max
|
||||
val_width = data.map { |n| n[1].size }.max
|
||||
|
||||
table = Tablo::Table.new(data, header_frequency: nil) do |t|
|
||||
t.add_column("Option", width: opt_width) { |n| n[0] }
|
||||
t.add_column("Value", width: val_width) { |n| n[1] }
|
||||
end
|
||||
puts table
|
||||
end
|
||||
end
|
||||
|
|
@ -1,22 +1,28 @@
|
|||
require "cling"
|
||||
require "file_utils"
|
||||
require "./base"
|
||||
require "../dirs"
|
||||
require "../config"
|
||||
require "../options"
|
||||
require "../options/*"
|
||||
require "../../tmux"
|
||||
require "colorize"
|
||||
|
||||
class Fingers::Commands::LoadConfig < Fingers::Commands::Base
|
||||
class Fingers::Commands::LoadConfig < Cling::Command
|
||||
@fingers_options_names : Array(String) | Nil
|
||||
|
||||
property config : Fingers::Config
|
||||
property config : Fingers::Config = Fingers::Config.new
|
||||
|
||||
DISALLOWED_CHARS = /cimqn/
|
||||
PRIVATE_OPTIONS = [
|
||||
"cli"
|
||||
]
|
||||
|
||||
def initialize(*args)
|
||||
super(*args)
|
||||
def setup : Nil
|
||||
@config = Fingers::Config.new
|
||||
@name = "load-config"
|
||||
end
|
||||
|
||||
def run
|
||||
def run(arguments, options) : Nil
|
||||
validate_options!
|
||||
parse_tmux_conf
|
||||
setup_bindings
|
||||
|
|
@ -27,74 +33,45 @@ class Fingers::Commands::LoadConfig < Fingers::Commands::Base
|
|||
def parse_tmux_conf
|
||||
options = shell_safe_options
|
||||
|
||||
user_defined_patterns = [] of String
|
||||
|
||||
Fingers.reset_config
|
||||
|
||||
config.tmux_version = `tmux -V`.chomp.split(" ").last
|
||||
|
||||
options.each do |option, value|
|
||||
# TODO generate an enum somehow and use an exhaustive case
|
||||
case option
|
||||
when "key"
|
||||
config.key = value
|
||||
when "keyboard_layout"
|
||||
config.keyboard_layout = value
|
||||
when "main_action"
|
||||
config.main_action = value
|
||||
when "ctrl_action"
|
||||
config.ctrl_action = value
|
||||
when "alt_action"
|
||||
config.alt_action = value
|
||||
when "shift_action"
|
||||
config.shift_action = value
|
||||
when "benchmark_mode"
|
||||
config.benchmark_mode = value
|
||||
when "hint_position"
|
||||
config.hint_position = value
|
||||
when "hint_style"
|
||||
config.hint_style = tmux.parse_style(value)
|
||||
when "selected_hint_style"
|
||||
config.selected_hint_style = tmux.parse_style(value)
|
||||
when "highlight_style"
|
||||
config.highlight_style = tmux.parse_style(value)
|
||||
when "backdrop_style"
|
||||
config.backdrop_style = tmux.parse_style(value)
|
||||
when "selected_highlight_style"
|
||||
config.selected_highlight_style = tmux.parse_style(value)
|
||||
end
|
||||
|
||||
if option.match(/pattern/)
|
||||
check_pattern!(value)
|
||||
user_defined_patterns.push(value)
|
||||
end
|
||||
Fingers::Options.parse(option, value, config)
|
||||
end
|
||||
|
||||
config.patterns = clean_up_patterns([
|
||||
*enabled_default_patterns,
|
||||
*user_defined_patterns,
|
||||
])
|
||||
add_builtin_patterns
|
||||
|
||||
config.alphabet = ::Fingers::Config::ALPHABET_MAP[Fingers.config.keyboard_layout].split("").reject do |char|
|
||||
char.match(DISALLOWED_CHARS)
|
||||
end
|
||||
config.tmux_version = tmux_version
|
||||
|
||||
config.save
|
||||
|
||||
Fingers.reset_config
|
||||
rescue e : TmuxStylePrinter::InvalidFormat
|
||||
puts "[tmux-fingers] #{e.message}"
|
||||
exit(1)
|
||||
end
|
||||
|
||||
def clean_up_patterns(patterns)
|
||||
patterns.reject(&.empty?)
|
||||
def add_builtin_patterns
|
||||
pattern_names = [] of String
|
||||
|
||||
if config.enabled_builtin_patterns == "all"
|
||||
pattern_names = ::Fingers::BUILTIN_PATTERNS.keys
|
||||
else
|
||||
pattern_names = config.enabled_builtin_patterns.split(",")
|
||||
end
|
||||
|
||||
pattern_names.each do |name|
|
||||
pattern = Fingers::BUILTIN_PATTERNS[name]?
|
||||
config.patterns[name.to_s] = pattern if pattern
|
||||
end
|
||||
end
|
||||
|
||||
def setup_bindings
|
||||
`tmux bind-key #{Fingers.config.key} run-shell -b "#{cli} start "\#{pane_id}" self >>#{Fingers::Dirs::LOG_PATH} 2>&1"`
|
||||
`tmux bind-key O run-shell -b "#{cli} start "\#{pane_id}" other >>#{Fingers::Dirs::LOG_PATH} 2>&1"`
|
||||
setup_root_bindings if Fingers.config.enable_bindings
|
||||
setup_fingers_mode_bindings
|
||||
`tmux set-option -g @fingers-cli #{cli}`
|
||||
end
|
||||
|
||||
def setup_root_bindings
|
||||
`tmux bind-key #{Fingers.config.key} run-shell -b "#{cli} start "\#{pane_id}" >>#{Fingers::Dirs::LOG_PATH} 2>&1"`
|
||||
`tmux bind-key #{Fingers.config.jump_key} run-shell -b "#{cli} start --mode jump "\#{pane_id}" >>#{Fingers::Dirs::LOG_PATH} 2>&1"`
|
||||
end
|
||||
|
||||
def setup_fingers_mode_bindings
|
||||
|
|
@ -120,32 +97,19 @@ class Fingers::Commands::LoadConfig < Fingers::Commands::Base
|
|||
fingers_mode_bind("Any", "noop")
|
||||
end
|
||||
|
||||
def enabled_default_patterns
|
||||
::Fingers::Config::DEFAULT_PATTERNS.values
|
||||
end
|
||||
|
||||
def to_bool(input)
|
||||
input == "1"
|
||||
end
|
||||
|
||||
def shell_safe_options
|
||||
options = {} of String => String
|
||||
|
||||
fingers_options_names.each do |option|
|
||||
option_method = option_to_method(option)
|
||||
fingers_options_names.each do |tmux_option|
|
||||
option = from_tmux_option(tmux_option)
|
||||
next if PRIVATE_OPTIONS.includes?(option)
|
||||
|
||||
options[option_method] = `tmux show-option -gv #{option}`.chomp
|
||||
options[option] = `tmux show-option -gv #{tmux_option}`.chomp
|
||||
end
|
||||
|
||||
options
|
||||
end
|
||||
|
||||
def valid_option?(option)
|
||||
option_method = option_to_method(option)
|
||||
|
||||
@config.members.includes?(option_method) || option_method.match(/pattern_[0-9]+/) || option_method == "skip_wizard"
|
||||
end
|
||||
|
||||
def fingers_options_names
|
||||
@fingers_options_names ||= `tmux show-options -g | grep ^@fingers`
|
||||
.chomp.split("\n")
|
||||
|
|
@ -153,39 +117,30 @@ class Fingers::Commands::LoadConfig < Fingers::Commands::Base
|
|||
.reject { |option| option.empty? }
|
||||
end
|
||||
|
||||
def unset_tmux_option!(option)
|
||||
`tmux set-option -ug #{option}`
|
||||
end
|
||||
def validate_options!
|
||||
options = shell_safe_options
|
||||
|
||||
def check_pattern!(pattern)
|
||||
begin
|
||||
Regex.new(pattern)
|
||||
rescue e: ArgumentError
|
||||
puts "[tmux-fingers] Invalid pattern: #{pattern}"
|
||||
puts "[tmux-fingers] #{e.message}"
|
||||
errors = [] of String
|
||||
|
||||
options.each do |option, value|
|
||||
is_valid, message = Fingers::Options.valid?(option, value)
|
||||
|
||||
errors << "#{to_tmux_option(option).colorize.bold}: #{message}" unless is_valid
|
||||
end
|
||||
|
||||
unless errors.empty?
|
||||
puts "[tmux-fingers] Configuration errors:".colorize(:red)
|
||||
errors.each { |error| puts " - #{error}" }
|
||||
exit(1)
|
||||
end
|
||||
end
|
||||
|
||||
def validate_options!
|
||||
errors = [] of String
|
||||
|
||||
fingers_options_names.each do |option|
|
||||
unless valid_option?(option)
|
||||
errors << "'#{option}' is not a valid option"
|
||||
unset_tmux_option!(option)
|
||||
end
|
||||
end
|
||||
|
||||
return if errors.empty?
|
||||
|
||||
puts "[tmux-fingers] Errors found in tmux.conf:"
|
||||
errors.each { |error| puts " - #{error}" }
|
||||
exit(1)
|
||||
def from_tmux_option(value)
|
||||
value.gsub(/^@fingers-/, "").tr("-", "_")
|
||||
end
|
||||
|
||||
def option_to_method(option)
|
||||
option.gsub(/^@fingers-/, "").tr("-", "_")
|
||||
def to_tmux_option(value)
|
||||
"@fingers-#{value.to_s.tr("_", "-")}"
|
||||
end
|
||||
|
||||
def fingers_mode_bind(key, command)
|
||||
|
|
@ -197,6 +152,10 @@ class Fingers::Commands::LoadConfig < Fingers::Commands::Base
|
|||
end
|
||||
|
||||
def tmux
|
||||
Tmux.new(`tmux -V`.chomp.split(" ").last)
|
||||
Tmux.new(tmux_version)
|
||||
end
|
||||
|
||||
def tmux_version
|
||||
`tmux -V`.chomp.split(" ").last
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,11 +1,18 @@
|
|||
require "./base"
|
||||
require "cling"
|
||||
|
||||
module Fingers::Commands
|
||||
class SendInput < Base
|
||||
def run
|
||||
class SendInput < Cling::Command
|
||||
def setup : Nil
|
||||
@name = "send-input"
|
||||
@hidden = true
|
||||
add_argument "input", required: true
|
||||
end
|
||||
|
||||
def run(arguments, options) : Nil
|
||||
socket = InputSocket.new
|
||||
|
||||
socket.send_message(@args[0])
|
||||
socket.send_message(arguments.get("input"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
require "cling"
|
||||
require "./base"
|
||||
require "../hinter"
|
||||
require "../view"
|
||||
|
|
@ -24,27 +25,121 @@ module Fingers::Commands
|
|||
end
|
||||
end
|
||||
|
||||
class Start < Base
|
||||
class Start < Cling::Command
|
||||
@original_options : Hash(String, String) = {} of String => String
|
||||
@last_key_table : String = "root"
|
||||
@last_pane_id : String | Nil
|
||||
@mode : String = "default"
|
||||
@pane_id : String = ""
|
||||
@active_pane : Tmux::Pane | Nil
|
||||
@patterns : Array(String) = [] of String
|
||||
@main_action : String | Nil
|
||||
@ctrl_action : String | Nil
|
||||
@shift_action : String | Nil
|
||||
@alt_action : String | Nil
|
||||
|
||||
def setup : Nil
|
||||
@name = "start"
|
||||
add_argument "pane_id",
|
||||
description: "pane id (also accepts tmux target-pane tokens specified in tmux man pages)",
|
||||
required: true
|
||||
add_option "mode",
|
||||
description: "can be \"jump\" or \"default\"",
|
||||
type: :single,
|
||||
default: "default"
|
||||
|
||||
add_option "patterns",
|
||||
description: "comma separated list of pattern names",
|
||||
type: :single
|
||||
|
||||
add_option "main-action",
|
||||
description: "command to which the output will be piped",
|
||||
type: :single
|
||||
|
||||
add_option "ctrl-action",
|
||||
description: "command to which the output will be piped when holding CTRL key",
|
||||
type: :single
|
||||
|
||||
add_option "alt-action",
|
||||
description: "command to which the output will be piped when holding ALT key",
|
||||
type: :single
|
||||
|
||||
add_option "shift-action",
|
||||
description: "command to which the output will be pipedwhen holding SHIFT key",
|
||||
type: :single
|
||||
|
||||
add_option 'h', "help", description: "prints help"
|
||||
end
|
||||
|
||||
def pre_run(arguments, options) : Bool
|
||||
if options.has?("help")
|
||||
puts help_template
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def run(arguments, options) : Nil
|
||||
@mode = options.get("mode").as_s
|
||||
parse_pane_target_format!(arguments.get("pane_id").as_s)
|
||||
|
||||
if options.has?("patterns")
|
||||
@patterns = patterns_from_options(options.get("patterns").as_s)
|
||||
else
|
||||
@patterns = Fingers.config.patterns.values
|
||||
end
|
||||
|
||||
@main_action = options.get?("main-action").try(&.as_s)
|
||||
@ctrl_action = options.get?("ctrl-action").try(&.as_s)
|
||||
@alt_action = options.get?("alt-action").try(&.as_s)
|
||||
@shift_action = options.get?("shift-action").try(&.as_s)
|
||||
|
||||
track_tmux_state
|
||||
|
||||
def run
|
||||
track_options_to_restore!
|
||||
show_hints
|
||||
|
||||
if Fingers.config.benchmark_mode == "1"
|
||||
if Fingers.config.benchmark_mode
|
||||
exit(0)
|
||||
end
|
||||
|
||||
handle_input
|
||||
|
||||
process_result
|
||||
teardown
|
||||
end
|
||||
|
||||
private def track_options_to_restore!
|
||||
options_to_preserve.each do |option|
|
||||
value = tmux.get_global_option(option)
|
||||
@original_options[option] = value
|
||||
private def patterns_from_options(pattern_names_option : String)
|
||||
pattern_names = pattern_names_option.split(",")
|
||||
|
||||
result = [] of String
|
||||
|
||||
pattern_names.each do |pattern_name|
|
||||
pattern = Fingers.config.patterns[pattern_name]?
|
||||
if pattern
|
||||
result << pattern
|
||||
else
|
||||
tmux.display_message("[tmux-fingers] error: Unknown pattern #{pattern_name}", 5000)
|
||||
exit 0
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
private def track_tmux_state
|
||||
output = tmux.exec("display-message -t '{last}' -p '\#{pane_id};\#{client_key_table};\#{prefix};\#{prefix2}'").chomp
|
||||
|
||||
last_pane_id, last_key_table, prefix, prefix2 = output.split(";")
|
||||
|
||||
@last_pane_id = last_pane_id
|
||||
@last_key_table = if last_key_table.empty?
|
||||
"root"
|
||||
else
|
||||
last_key_table
|
||||
end
|
||||
|
||||
@original_options["prefix"] = prefix
|
||||
@original_options["prefix2"] = prefix2
|
||||
end
|
||||
|
||||
private def restore_options
|
||||
|
|
@ -53,22 +148,48 @@ module Fingers::Commands
|
|||
end
|
||||
end
|
||||
|
||||
private def restore_last_key_table
|
||||
tmux.set_key_table(@last_key_table)
|
||||
end
|
||||
|
||||
private def restore_last_pane
|
||||
tmux.select_pane(@last_pane_id)
|
||||
select_active_pane
|
||||
end
|
||||
|
||||
private def options_to_preserve
|
||||
%w[prefix prefix2]
|
||||
end
|
||||
|
||||
private def parse_pane_target_format!(pane_target_format)
|
||||
if pane_target_format.match(/^%[0-9]+$/)
|
||||
@pane_id = pane_target_format
|
||||
@active_pane = target_pane
|
||||
else
|
||||
@pane_id = tmux.exec("display-message -t #{pane_target_format} -p '\#{pane_id}'").chomp
|
||||
@active_pane = tmux.list_panes("\#{pane_active}", target_pane.window_id).first
|
||||
end
|
||||
end
|
||||
|
||||
private def show_hints
|
||||
# Attention! It is very important to resize the window at this point to
|
||||
# match the dimensions of the target pane. Otherwise weird linejumping
|
||||
# will occur when we have wrapped lines.
|
||||
# It is very important to resize the window at this point to match the
|
||||
# dimensions of the target pane. Otherwise weird linejumping will occur
|
||||
# when we have wrapped lines.
|
||||
tmux.resize_window(
|
||||
fingers_window.window_id,
|
||||
target_pane.pane_width,
|
||||
target_pane.pane_height,
|
||||
) if needs_resize?
|
||||
|
||||
view.render
|
||||
tmux.swap_panes(fingers_window.pane_id, target_pane.pane_id)
|
||||
# Swapping panes with -Z flag causes some issues with rendering panes
|
||||
# with tabs or double width characters
|
||||
if target_pane.window_zoomed_flag
|
||||
tmux.swap_panes(fingers_window.pane_id, target_pane.pane_id)
|
||||
view.render
|
||||
else
|
||||
view.render
|
||||
tmux.swap_panes(fingers_window.pane_id, target_pane.pane_id)
|
||||
end
|
||||
end
|
||||
|
||||
private def handle_input
|
||||
|
|
@ -83,23 +204,61 @@ module Fingers::Commands
|
|||
end
|
||||
end
|
||||
|
||||
private def process_result
|
||||
return unless state.result
|
||||
|
||||
match = hinter.lookup(state.input)
|
||||
|
||||
ActionRunner.new(
|
||||
hint: state.input,
|
||||
modifier: state.modifier,
|
||||
match: state.result,
|
||||
original_pane: active_pane,
|
||||
offset: match ? match.not_nil!.offset : nil,
|
||||
mode: mode,
|
||||
main_action: @main_action,
|
||||
ctrl_action: @ctrl_action,
|
||||
alt_action: @alt_action,
|
||||
shift_action: @shift_action,
|
||||
).run
|
||||
|
||||
tmux.display_message("Copied: #{state.result}", 1000) if should_notify?
|
||||
rescue e
|
||||
Log.error { "Error processing result: #{e.message}" }
|
||||
|
||||
tmux.display_message("#[bg=red,fg=white,bold][tmux-fingers]#[bg=red,fg=black,nobold] #{e.message}".ljust(target_pane.pane_width * 2, ' '), 3000)
|
||||
end
|
||||
|
||||
private def select_active_pane
|
||||
tmux.select_pane(active_pane.pane_id)
|
||||
end
|
||||
|
||||
private def needs_resize?
|
||||
pane_width = target_pane.pane_width.to_i
|
||||
pane_contents.any? { |line| line.size > pane_width }
|
||||
pane_contents.any? do |line|
|
||||
line.bytesize > line.size || line.size > pane_width
|
||||
end
|
||||
end
|
||||
|
||||
private def teardown
|
||||
tmux.set_key_table "root"
|
||||
|
||||
tmux.swap_panes(fingers_pane_id, target_pane.pane_id)
|
||||
tmux.kill_pane(fingers_pane_id)
|
||||
|
||||
restore_last_pane
|
||||
restore_last_key_table
|
||||
restore_options
|
||||
view.run_action if state.result
|
||||
end
|
||||
|
||||
private getter target_pane : Tmux::Pane do
|
||||
tmux.find_pane_by_id(@args[0]).not_nil!
|
||||
tmux.find_pane_by_id(@pane_id).not_nil!
|
||||
end
|
||||
|
||||
private getter active_pane : Tmux::Pane do
|
||||
@active_pane.not_nil!
|
||||
end
|
||||
|
||||
private getter mode : String do
|
||||
@mode.not_nil!
|
||||
end
|
||||
|
||||
private getter fingers_window : Tmux::Window do
|
||||
|
|
@ -121,14 +280,16 @@ module Fingers::Commands
|
|||
private getter hinter : Hinter do
|
||||
Fingers::Hinter.new(
|
||||
input: pane_contents,
|
||||
patterns: @patterns,
|
||||
width: target_pane.pane_width.to_i,
|
||||
state: state,
|
||||
output: pane_printer
|
||||
output: pane_printer,
|
||||
reuse_hints: mode != "jump",
|
||||
)
|
||||
end
|
||||
|
||||
private getter pane_contents : Array(String) do
|
||||
tmux.capture_pane(target_pane).split("\n")
|
||||
tmux.capture_pane(target_pane, join: mode != "jump").split("\n")
|
||||
end
|
||||
|
||||
private getter view : View do
|
||||
|
|
@ -137,12 +298,17 @@ module Fingers::Commands
|
|||
state: state,
|
||||
output: pane_printer,
|
||||
original_pane: target_pane,
|
||||
tmux: tmux
|
||||
tmux: tmux,
|
||||
mode: mode,
|
||||
)
|
||||
end
|
||||
|
||||
private getter tmux : Tmux do
|
||||
Tmux.new(Fingers.config.tmux_version)
|
||||
Tmux.new("3.3a")
|
||||
end
|
||||
|
||||
private def should_notify?
|
||||
!state.result.empty? && Fingers.config.show_copied_notification == "1"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,8 +1,14 @@
|
|||
require "./base"
|
||||
require "cling"
|
||||
|
||||
module Fingers::Commands
|
||||
class Version < Base
|
||||
def run
|
||||
class Version < Cling::Command
|
||||
def setup : Nil
|
||||
@name = "version"
|
||||
@description = "Duh."
|
||||
end
|
||||
|
||||
def run(arguments, options) : Nil
|
||||
puts "#{Fingers::VERSION}"
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,84 +1,35 @@
|
|||
require "json"
|
||||
require "./options"
|
||||
require "./options/*"
|
||||
|
||||
module Fingers
|
||||
struct Config
|
||||
class Config
|
||||
include JSON::Serializable
|
||||
|
||||
property key : String
|
||||
property keyboard_layout : String
|
||||
property patterns : Array(String)
|
||||
property alphabet : Array(String)
|
||||
property benchmark_mode : String
|
||||
property main_action : String
|
||||
property ctrl_action : String
|
||||
property alt_action : String
|
||||
property shift_action : String
|
||||
property hint_position : String
|
||||
property hint_style : String
|
||||
property selected_hint_style : String
|
||||
property highlight_style : String
|
||||
property selected_highlight_style : String
|
||||
property backdrop_style : String
|
||||
property tmux_version : String
|
||||
define_fingers_config_struct_properties
|
||||
|
||||
FORMAT_PRINTER = TmuxStylePrinter.new
|
||||
|
||||
DEFAULT_PATTERNS = {
|
||||
BUILTIN_PATTERNS = {
|
||||
"ip": "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}",
|
||||
"uuid": "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
|
||||
"sha": "[0-9a-f]{7,128}",
|
||||
"digit": "[0-9]{4,}",
|
||||
"url": "((https?://|git@|git://|ssh://|ftp://|file:///)[^\\s()\"]+)",
|
||||
"url": "((https?://|git@|git://|ssh://|ftp://|file:///)[^\\s()\"']+)",
|
||||
"path": "(([.\\w\\-~\\$@]+)?(/[.\\w\\-@]+)+/?)",
|
||||
"hex": "(0x[0-9a-fA-F]+)",
|
||||
"kubernetes": "(deployment.app|binding|componentstatuse|configmap|endpoint|event|limitrange|namespace|node|persistentvolumeclaim|persistentvolume|pod|podtemplate|replicationcontroller|resourcequota|secret|serviceaccount|service|mutatingwebhookconfiguration.admissionregistration.k8s.io|validatingwebhookconfiguration.admissionregistration.k8s.io|customresourcedefinition.apiextension.k8s.io|apiservice.apiregistration.k8s.io|controllerrevision.apps|daemonset.apps|deployment.apps|replicaset.apps|statefulset.apps|tokenreview.authentication.k8s.io|localsubjectaccessreview.authorization.k8s.io|selfsubjectaccessreviews.authorization.k8s.io|selfsubjectrulesreview.authorization.k8s.io|subjectaccessreview.authorization.k8s.io|horizontalpodautoscaler.autoscaling|cronjob.batch|job.batch|certificatesigningrequest.certificates.k8s.io|events.events.k8s.io|daemonset.extensions|deployment.extensions|ingress.extensions|networkpolicies.extensions|podsecuritypolicies.extensions|replicaset.extensions|networkpolicie.networking.k8s.io|poddisruptionbudget.policy|clusterrolebinding.rbac.authorization.k8s.io|clusterrole.rbac.authorization.k8s.io|rolebinding.rbac.authorization.k8s.io|role.rbac.authorization.k8s.io|storageclasse.storage.k8s.io)[[:alnum:]_#$%&+=/@-]+",
|
||||
"git-status": "(modified|deleted|new file): +(?<match>.+)",
|
||||
# Matches deployment-managed pod names like "nginx-deployment-66b6c48dd5-7xb2r".
|
||||
# K8s generates hashes using a restricted alphabet (no vowels, no 0/1/3)
|
||||
# to avoid accidental profanity: "bcdfghjklmnpqrstvwxz2456789"
|
||||
# See: https://github.com/kubernetes/apimachinery/blob/master/pkg/util/rand/rand.go
|
||||
"kubernetes-pod": "[a-z][a-z0-9-]*[a-z0-9]-[bcdfghjklmnpqrstvwxz2456789]{5,10}-[bcdfghjklmnpqrstvwxz2456789]{5}",
|
||||
"git-status": "(modified|deleted|deleted by us|new file): +(?<match>.+)",
|
||||
"git-status-branch": "Your branch is up to date with '(?<match>.*)'.",
|
||||
"diff": "(---|\\+\\+\\+) [ab]/(?<match>.*)",
|
||||
}
|
||||
|
||||
ALPHABET_MAP = {
|
||||
"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",
|
||||
}
|
||||
|
||||
def initialize(
|
||||
@key = "F",
|
||||
@keyboard_layout = "qwerty",
|
||||
@alphabet = [] of String,
|
||||
@patterns = [] of String,
|
||||
@main_action = ":copy:",
|
||||
@ctrl_action = ":open:",
|
||||
@alt_action = "",
|
||||
@shift_action = ":paste:",
|
||||
@hint_position = "left",
|
||||
@hint_style = FORMAT_PRINTER.print("fg=green,bold"),
|
||||
@highlight_style = FORMAT_PRINTER.print("fg=yellow"),
|
||||
@selected_hint_style = FORMAT_PRINTER.print("fg=blue,bold"),
|
||||
@selected_highlight_style = FORMAT_PRINTER.print("fg=blue"),
|
||||
@backdrop_style = "",
|
||||
@tmux_version = "",
|
||||
@benchmark_mode = "0"
|
||||
)
|
||||
end
|
||||
define_fingers_config_struct_initializer
|
||||
|
||||
def self.load_from_cache
|
||||
Config.from_json(File.open(::Fingers::Dirs::CONFIG_PATH))
|
||||
|
|
@ -91,6 +42,14 @@ module Fingers
|
|||
def members : Array(String)
|
||||
JSON.parse(to_json).as_h.keys
|
||||
end
|
||||
|
||||
# computed properties
|
||||
|
||||
def alphabet
|
||||
Fingers::ALPHABET_MAP[keyboard_layout].split("").reject do |char|
|
||||
char.match(Fingers::DISALLOWED_CHARS)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.config
|
||||
|
|
|
|||
53
src/fingers/constants.cr
Normal file
53
src/fingers/constants.cr
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
module Fingers
|
||||
DISALLOWED_CHARS = /[cimqn]/
|
||||
|
||||
ALPHABET_MAP = {
|
||||
"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",
|
||||
}
|
||||
|
||||
BUILTIN_PATTERNS = {
|
||||
"ip": "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}",
|
||||
"uuid": "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
|
||||
"sha": "[0-9a-f]{7,128}",
|
||||
"digit": "[0-9]{4,}",
|
||||
"url": "((https?://|git@|git://|ssh://|ftp://|file:///)[^\\s()\"']+)",
|
||||
"path": "(([.\\w\\-~\\$@]+)?(/[.\\w\\-@]+)+/?)",
|
||||
"hex": "(0x[0-9a-fA-F]+)",
|
||||
"kubernetes": "(deployment.app|binding|componentstatuse|configmap|endpoint|event|limitrange|namespace|node|persistentvolumeclaim|persistentvolume|pod|podtemplate|replicationcontroller|resourcequota|secret|serviceaccount|service|mutatingwebhookconfiguration.admissionregistration.k8s.io|validatingwebhookconfiguration.admissionregistration.k8s.io|customresourcedefinition.apiextension.k8s.io|apiservice.apiregistration.k8s.io|controllerrevision.apps|daemonset.apps|deployment.apps|replicaset.apps|statefulset.apps|tokenreview.authentication.k8s.io|localsubjectaccessreview.authorization.k8s.io|selfsubjectaccessreviews.authorization.k8s.io|selfsubjectrulesreview.authorization.k8s.io|subjectaccessreview.authorization.k8s.io|horizontalpodautoscaler.autoscaling|cronjob.batch|job.batch|certificatesigningrequest.certificates.k8s.io|events.events.k8s.io|daemonset.extensions|deployment.extensions|ingress.extensions|networkpolicies.extensions|podsecuritypolicies.extensions|replicaset.extensions|networkpolicie.networking.k8s.io|poddisruptionbudget.policy|clusterrolebinding.rbac.authorization.k8s.io|clusterrole.rbac.authorization.k8s.io|rolebinding.rbac.authorization.k8s.io|role.rbac.authorization.k8s.io|storageclasse.storage.k8s.io)[[:alnum:]_#$%&+=/@-]+",
|
||||
# Matches deployment-managed pod names like "nginx-deployment-66b6c48dd5-7xb2r".
|
||||
# K8s generates hashes using a restricted alphabet (no vowels, no 0/1/3)
|
||||
# to avoid accidental profanity: "bcdfghjklmnpqrstvwxz2456789"
|
||||
# See: https://github.com/kubernetes/apimachinery/blob/master/pkg/util/rand/rand.go
|
||||
"kubernetes-pod": "[a-z][a-z0-9-]*[a-z0-9]-[bcdfghjklmnpqrstvwxz2456789]{5,10}-[bcdfghjklmnpqrstvwxz2456789]{5}",
|
||||
"git-status": "(modified|deleted|deleted by us|new file): +(?<match>.+)",
|
||||
"git-status-branch": "Your branch is up to date with '(?<match>.*)'.",
|
||||
"diff": "(---|\\+\\+\\+) [ab]/(?<match>.*)",
|
||||
}
|
||||
|
||||
COPY_ACTION = ":copy:"
|
||||
PASTE_ACTION = ":paste:"
|
||||
OPEN_ACTION = ":open:"
|
||||
|
||||
ACTIONS = [COPY_ACTION, PASTE_ACTION, OPEN_ACTION]
|
||||
|
||||
ANSI_RESET = "\e[0m"
|
||||
end
|
||||
|
|
@ -1,17 +1,34 @@
|
|||
require "file_utils"
|
||||
require "xdg_base_directory"
|
||||
|
||||
# TODO maybe use some xgd shite here?
|
||||
def running_in_specs? : Bool
|
||||
{{ @type.has_constant?("Spec") }}
|
||||
end
|
||||
|
||||
module Fingers::Dirs
|
||||
TMUX_PID = (ENV["TMUX"] || ",0000").split(",")[1]
|
||||
# When running in specs use current PID, to avoid using existing cached data
|
||||
TMUX_PID = running_in_specs? ? Process.pid : (ENV.fetch("TMUX", ",0000")).split(",")[1]
|
||||
XDG = XdgBaseDirectory.app_directories("tmux-fingers")
|
||||
|
||||
ROOT = Path["/tmp"] / "tmux-#{TMUX_PID}"
|
||||
TMP = Path[File.dirname(File.tempname)]
|
||||
|
||||
LOG_PATH = ROOT / "fingers.log"
|
||||
CACHE = ROOT / "tmux-fingers"
|
||||
ROOT = Path[XDG.state.file_path("tmux-#{TMUX_PID}")]
|
||||
|
||||
{% if env("FINGERS_LOG_PATH") %}
|
||||
# used in development to read logs outside container more easily
|
||||
LOG_PATH = {{ env("FINGERS_LOG_PATH") }}
|
||||
{% else %}
|
||||
LOG_PATH = Path[XDG.state.file_path("fingers.log")]
|
||||
{% end %}
|
||||
|
||||
CACHE = ROOT
|
||||
CONFIG_PATH = CACHE / "config.json"
|
||||
SOCKET_PATH = CACHE / "fingers.sock"
|
||||
|
||||
# ensure cache folder
|
||||
FileUtils.mkdir_p(Fingers::Dirs::CACHE) unless File.exists?(Fingers::Dirs::CACHE)
|
||||
def self.ensure_folders!
|
||||
Fingers::Dirs::XDG.state.mkdir unless Fingers::Dirs::XDG.state.exists?
|
||||
FileUtils.mkdir_p(Fingers::Dirs::ROOT) unless File.exists?(Fingers::Dirs::ROOT)
|
||||
end
|
||||
|
||||
Fingers::Dirs.ensure_folders!
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,6 +4,15 @@ require "./match_formatter"
|
|||
require "./types"
|
||||
|
||||
module Fingers
|
||||
struct Target
|
||||
property text : String
|
||||
property hint : String
|
||||
property offset : Tuple(Int32, Int32)
|
||||
|
||||
def initialize(@text, @hint, @offset)
|
||||
end
|
||||
end
|
||||
|
||||
class Hinter
|
||||
@formatter : Formatter
|
||||
@patterns : Array(String)
|
||||
|
|
@ -11,6 +20,7 @@ module Fingers
|
|||
@pattern : Regex | Nil
|
||||
@hints : Array(String) | Nil
|
||||
@n_matches : Int32 | Nil
|
||||
@reuse_hints : Bool
|
||||
|
||||
def initialize(
|
||||
input : Array(String),
|
||||
|
|
@ -20,42 +30,39 @@ module Fingers
|
|||
patterns = Fingers.config.patterns,
|
||||
alphabet = Fingers.config.alphabet,
|
||||
huffman = Huffman.new,
|
||||
formatter = ::Fingers::MatchFormatter.new
|
||||
formatter = ::Fingers::MatchFormatter.new,
|
||||
reuse_hints = false
|
||||
)
|
||||
@lines = input
|
||||
@width = width
|
||||
@hints_by_text = {} of String => String
|
||||
@lookup_table = {} of String => String
|
||||
@target_by_hint = {} of String => Target
|
||||
@target_by_text = {} of String => Target
|
||||
@state = state
|
||||
@output = output
|
||||
@formatter = formatter
|
||||
@huffman = huffman
|
||||
@patterns = patterns
|
||||
@alphabet = alphabet
|
||||
@reuse_hints = reuse_hints
|
||||
end
|
||||
|
||||
def run
|
||||
lines[0..-2].each { |line| process_line(line, "\n") }
|
||||
process_line(lines[-1], "")
|
||||
regenerate_hints!
|
||||
lines[0..-2].each_with_index { |line, index| process_line(line, index, "\n") }
|
||||
process_line(lines[-1], lines.size - 1, "")
|
||||
|
||||
# STDOUT.flush
|
||||
output.flush
|
||||
|
||||
build_lookup_table!
|
||||
end
|
||||
|
||||
def lookup(hint)
|
||||
lookup_table.fetch(hint) { nil }
|
||||
end
|
||||
|
||||
def matches
|
||||
@matches ||= @hints_by_text.keys.uniq!.flatten
|
||||
def lookup(hint) : Target | Nil
|
||||
target_by_hint.fetch(hint) { nil }
|
||||
end
|
||||
|
||||
# private
|
||||
|
||||
private getter :hints,
|
||||
:hints_by_text,
|
||||
:offsets_by_hint,
|
||||
:input,
|
||||
:lookup_table,
|
||||
:width,
|
||||
|
|
@ -64,17 +71,21 @@ module Fingers
|
|||
:huffman,
|
||||
:output,
|
||||
:patterns,
|
||||
:alphabet
|
||||
:alphabet,
|
||||
:reuse_hints,
|
||||
:target_by_hint,
|
||||
:target_by_text
|
||||
|
||||
def build_lookup_table!
|
||||
@lookup_table = hints_by_text.invert
|
||||
end
|
||||
def process_line(line, line_index, ending)
|
||||
tab_positions = tab_positions_for(line)
|
||||
result = line.gsub(pattern) { |_m| replace($~, line_index) }
|
||||
initial_length = result.size
|
||||
result = expand_tabs(result, tab_positions)
|
||||
tab_correction = result.size - initial_length
|
||||
|
||||
def process_line(line, ending)
|
||||
result = line.gsub(pattern) { |_m| replace($~) }
|
||||
result = Fingers.config.backdrop_style + result
|
||||
double_width_correction = ((line.bytesize - line.size) / 3).round.to_i
|
||||
padding_amount = (width - line.size - double_width_correction)
|
||||
padding_amount = (width - line.size - double_width_correction - tab_correction)
|
||||
padding = padding_amount > 0 ? " " * padding_amount : ""
|
||||
output.print(result + padding + ending)
|
||||
end
|
||||
|
|
@ -86,40 +97,93 @@ module Fingers
|
|||
def hints : Array(String)
|
||||
return @hints.as(Array(String)) if !@hints.nil?
|
||||
|
||||
@hints = huffman.generate_hints(alphabet: alphabet, n: n_matches)
|
||||
regenerate_hints!
|
||||
|
||||
@hints.as(Array(String))
|
||||
end
|
||||
|
||||
def replace(match)
|
||||
def regenerate_hints!
|
||||
@hints = huffman.generate_hints(alphabet: alphabet.clone, n: n_matches)
|
||||
@target_by_hint.clear
|
||||
@target_by_text.clear
|
||||
end
|
||||
|
||||
def replace(match, line_index)
|
||||
text = match[0]
|
||||
|
||||
captured_text = match["match"]? || text
|
||||
captured_text = captured_text_for_match(match)
|
||||
relative_capture_offset = relative_capture_offset_for_match(match, captured_text)
|
||||
|
||||
if match["match"]?
|
||||
match_start, match_end = {match.begin(0), match.end(0)}
|
||||
capture_start, capture_end = find_capture_offset(match).not_nil!
|
||||
capture_offset = {capture_start - match_start, captured_text.size}
|
||||
else
|
||||
capture_offset = nil
|
||||
absolute_offset = {
|
||||
line_index,
|
||||
match.begin(0) + (relative_capture_offset ? relative_capture_offset[0] : 0)
|
||||
}
|
||||
|
||||
hint = hint_for_text(captured_text)
|
||||
|
||||
# hint is longer than highlighted text, put it back in hint stack
|
||||
if hint.size > captured_text.size
|
||||
hints.push(hint)
|
||||
return text
|
||||
end
|
||||
|
||||
if hints_by_text.has_key?(captured_text)
|
||||
hint = hints_by_text[captured_text]
|
||||
else
|
||||
hint = hints.pop
|
||||
build_target(captured_text, hint, absolute_offset)
|
||||
|
||||
raise "Too many matches" if hint.nil?
|
||||
|
||||
hints_by_text[captured_text] = hint
|
||||
if !state.input.empty? && !hint.starts_with?(state.input)
|
||||
return text
|
||||
end
|
||||
|
||||
formatter.format(
|
||||
hint: hint,
|
||||
highlight: text,
|
||||
selected: state.selected_hints.includes?(hint),
|
||||
offset: capture_offset
|
||||
offset: relative_capture_offset
|
||||
)
|
||||
end
|
||||
|
||||
def captured_text_for_match(match)
|
||||
match["match"]? || match[0]
|
||||
end
|
||||
|
||||
def hint_for_text(text)
|
||||
return pop_hint! unless reuse_hints
|
||||
|
||||
target = target_by_text[text]?
|
||||
|
||||
if target.nil?
|
||||
return pop_hint!
|
||||
end
|
||||
|
||||
target.hint
|
||||
end
|
||||
|
||||
def pop_hint! : String
|
||||
hint = hints.pop?
|
||||
|
||||
if hint.nil?
|
||||
raise "Too many matches"
|
||||
end
|
||||
|
||||
hint
|
||||
end
|
||||
|
||||
def relative_capture_offset_for_match(match, captured_text)
|
||||
return nil unless match["match"]?
|
||||
|
||||
match_start, match_end = {match.begin(0), match.end(0)}
|
||||
capture_start, capture_end = find_capture_offset(match).not_nil!
|
||||
{capture_start - match_start, captured_text.size}
|
||||
end
|
||||
|
||||
def build_target(text, hint, offset)
|
||||
target = Target.new(text, hint, offset)
|
||||
|
||||
target_by_hint[hint] = target
|
||||
target_by_text[text] = target
|
||||
|
||||
target
|
||||
end
|
||||
|
||||
def find_capture_offset(match : Regex::MatchData) : Tuple(Int32, Int32) | Nil
|
||||
index = capture_indices.find { |i| match[i]? }
|
||||
|
||||
|
|
@ -135,11 +199,19 @@ module Fingers
|
|||
def n_matches : Int32
|
||||
return @n_matches.as(Int32) if !@n_matches.nil?
|
||||
|
||||
if reuse_hints
|
||||
@n_matches = count_unique_matches
|
||||
else
|
||||
@n_matches = count_matches
|
||||
end
|
||||
end
|
||||
|
||||
def count_unique_matches
|
||||
match_set = Set(String).new
|
||||
|
||||
lines.each do |line|
|
||||
line.scan(pattern) do |match|
|
||||
match_set.add(match[0]?.not_nil!)
|
||||
match_set.add(captured_text_for_match(match))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -148,6 +220,44 @@ module Fingers
|
|||
match_set.size
|
||||
end
|
||||
|
||||
def count_matches
|
||||
result = 0
|
||||
|
||||
lines.each do |line|
|
||||
line.scan(pattern) do |match|
|
||||
result += 1
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def tab_positions_for(line)
|
||||
positions = [] of Int32
|
||||
offset = 0
|
||||
|
||||
loop do
|
||||
index = line.index("\t", offset)
|
||||
|
||||
break unless index
|
||||
positions << index
|
||||
offset = index + 1
|
||||
end
|
||||
|
||||
positions
|
||||
end
|
||||
|
||||
def expand_tabs(line, tab_positions)
|
||||
correction = 0
|
||||
line.gsub(/\t/) do |_|
|
||||
tab_position = tab_positions.shift?
|
||||
next "\t" unless tab_position
|
||||
spaces = 8 - ((tab_position + correction) % 8)
|
||||
correction += spaces - 1
|
||||
" " * spaces
|
||||
end
|
||||
end
|
||||
|
||||
private property lines : Array(String)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@ module Fingers
|
|||
selected_highlight_style : String = Fingers.config.selected_highlight_style,
|
||||
backdrop_style : String = Fingers.config.backdrop_style,
|
||||
hint_position : String = Fingers.config.hint_position,
|
||||
# TODO #perf remove this shell call
|
||||
reset_sequence : String = `tput sgr0`.chomp
|
||||
reset_sequence : String = Fingers::ANSI_RESET
|
||||
)
|
||||
@hint_style = hint_style
|
||||
@highlight_style = highlight_style
|
||||
|
|
@ -31,7 +30,7 @@ module Fingers
|
|||
private getter :hint_style, :highlight_style, :selected_hint_style, :selected_highlight_style, :hint_position, :reset_sequence, :backdrop_style
|
||||
|
||||
private def before_offset(offset, highlight)
|
||||
return "" if offset.nil?
|
||||
return "" if offset.nil? || offset[0] == 0
|
||||
start, _ = offset
|
||||
backdrop_style + highlight[0..(start - 1)]
|
||||
end
|
||||
|
|
@ -55,9 +54,9 @@ module Fingers
|
|||
highlight_pair = (selected ? selected_highlight_style : highlight_style) + chopped_highlight
|
||||
|
||||
if hint_position == "right"
|
||||
highlight_pair + reset_sequence + hint_pair + reset_sequence
|
||||
reset_sequence + highlight_pair + reset_sequence + hint_pair + reset_sequence
|
||||
else
|
||||
hint_pair + reset_sequence + highlight_pair + reset_sequence
|
||||
reset_sequence + hint_pair + reset_sequence + highlight_pair + reset_sequence
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
35
src/fingers/options.cr
Normal file
35
src/fingers/options.cr
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
require "./options/*"
|
||||
require "./options/parsers/*"
|
||||
require "../tmux_style_printer"
|
||||
|
||||
define_key_option :key, "F"
|
||||
define_key_option :jump_key, "J"
|
||||
|
||||
define_bool_option :use_system_clipboard, true
|
||||
define_bool_option :enable_bindings, true
|
||||
define_bool_option :benchmark_mode, false
|
||||
define_bool_option :show_copied_notification, true
|
||||
define_bool_option :skip_wizard, false
|
||||
|
||||
define_enum_option :hint_position, %w(left right), "left"
|
||||
define_enum_option :keyboard_layout, Fingers::ALPHABET_MAP.keys, "qwerty"
|
||||
define_multi_enum_option :enabled_builtin_patterns, ["all", *Fingers::BUILTIN_PATTERNS.keys], "all"
|
||||
|
||||
define_action_option :main, ":copy:"
|
||||
define_action_option :ctrl, ":open:"
|
||||
define_action_option :alt, ""
|
||||
define_action_option :shift, ":paste:"
|
||||
|
||||
define_style_option :hint, Tmux.style_printer.print("fg=green,bold")
|
||||
define_style_option :highlight, Tmux.style_printer.print("fg=yellow")
|
||||
define_style_option :selected_hint, Tmux.style_printer.print("fg=blue,bold")
|
||||
define_style_option :selected_highlight, Tmux.style_printer.print("fg=blue")
|
||||
define_style_option :backdrop, ""
|
||||
|
||||
define_string_option :tmux_version, ""
|
||||
|
||||
define_fingers_macros
|
||||
|
||||
module Fingers::Options
|
||||
define_fingers_options_module_helpers
|
||||
end
|
||||
30
src/fingers/options/base.cr
Normal file
30
src/fingers/options/base.cr
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
module Fingers::Options
|
||||
class Base
|
||||
# Type and DEFAULT are defined in subclasses
|
||||
macro inherited
|
||||
def write(value : Type, config : Fingers::Config)
|
||||
config.{{ @type.name.split("::").last.underscore.id }} = process(value)
|
||||
end
|
||||
end
|
||||
|
||||
def self.as_option_name
|
||||
self.name.split("::").last.underscore.downcase
|
||||
end
|
||||
|
||||
def parse(raw_value : String, option_name : String)
|
||||
raw_value
|
||||
end
|
||||
|
||||
def valid?(raw_value : String) : Tuple(Bool, String)
|
||||
{ true, "ok" }
|
||||
end
|
||||
|
||||
def process(value)
|
||||
value
|
||||
end
|
||||
|
||||
def ==(other : String)
|
||||
self.class.as_option_name == other
|
||||
end
|
||||
end
|
||||
end
|
||||
130
src/fingers/options/macros.cr
Normal file
130
src/fingers/options/macros.cr
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
macro define_fingers_macros
|
||||
{% option_names = Fingers::Options::Base.all_subclasses.map { |klass| klass.name.split("::").last } %}
|
||||
|
||||
macro define_fingers_config_struct_initializer
|
||||
{% begin %}
|
||||
def initialize(
|
||||
{% for option_name in option_names %}
|
||||
@{{ option_name.underscore.id }} = ::Fingers::Options::{{ option_name.id }}::DEFAULT,
|
||||
{% end %}
|
||||
)
|
||||
end
|
||||
{% end %}
|
||||
end
|
||||
|
||||
macro define_fingers_config_struct_properties
|
||||
{% for option_name in option_names %}
|
||||
property {{ option_name.underscore.id }} : Fingers::Options::{{ option_name.id }}::Type
|
||||
{% end %}
|
||||
end
|
||||
|
||||
macro define_fingers_options_module_helpers
|
||||
{% for option_name in option_names %}
|
||||
def self.{{option_name.underscore.id}}
|
||||
::Fingers::Options::{{option_name.id}}.new
|
||||
end
|
||||
{% end %}
|
||||
|
||||
def self.parse(option_name : String, option_value : String, config : Fingers::Config)
|
||||
{% begin %}
|
||||
case option_name
|
||||
{% for option_name in option_names %}
|
||||
when option = ::Fingers::Options.{{option_name.underscore.id}}
|
||||
parsed_value = option.parse(option_value, option_name)
|
||||
option.write(parsed_value, config)
|
||||
{% end %}
|
||||
end
|
||||
{% end %}
|
||||
end
|
||||
|
||||
def self.valid?(option_name : String, option_value : String)
|
||||
{% begin %}
|
||||
case option_name
|
||||
{% for option_name in option_names %}
|
||||
when option = ::Fingers::Options.{{option_name.underscore.id}}
|
||||
option.valid?(option_value)
|
||||
{% end %}
|
||||
else
|
||||
{ false, "Unknown option: #{option_name}" }
|
||||
end
|
||||
{% end %}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
macro define_bool_option(name, default)
|
||||
module Fingers::Options
|
||||
class {{name.camelcase.id}} < Base
|
||||
include ::Fingers::Options::Parsers::BoolParser
|
||||
DEFAULT = {{ default }}
|
||||
alias Type = Bool
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
macro define_enum_option(name, possible_values, default)
|
||||
module Fingers::Options
|
||||
class {{name.camelcase.id}} < Base
|
||||
include ::Fingers::Options::Parsers::EnumParser
|
||||
DEFAULT = {{ default }}
|
||||
alias Type = String
|
||||
|
||||
def possible_values
|
||||
{{ possible_values }}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
macro define_multi_enum_option(name, possible_values, default)
|
||||
module Fingers::Options
|
||||
class {{name.camelcase.id}} < Base
|
||||
include ::Fingers::Options::Parsers::MultiEnumParser
|
||||
DEFAULT = {{ default }}
|
||||
alias Type = String
|
||||
|
||||
def possible_values
|
||||
{{ possible_values }}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
macro define_key_option(name, default)
|
||||
module Fingers::Options
|
||||
class {{name.camelcase.id}} < Base
|
||||
DEFAULT = {{ default }}
|
||||
alias Type = String
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
macro define_string_option(name, default)
|
||||
module Fingers::Options
|
||||
class {{name.camelcase.id}} < Base
|
||||
DEFAULT = {{ default }}
|
||||
alias Type = String
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
macro define_action_option(name, default)
|
||||
module Fingers::Options
|
||||
class {{"#{name}_action".gsub(/^:/, "").camelcase.id}} < Base
|
||||
include ::Fingers::Options::Parsers::ActionParser
|
||||
DEFAULT = {{ default }}
|
||||
alias Type = String
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
macro define_style_option(name, default)
|
||||
module Fingers::Options
|
||||
class {{"#{name}_style".gsub(/^:/, "").camelcase.id}} < Base
|
||||
include ::Fingers::Options::Parsers::StyleParser
|
||||
|
||||
DEFAULT = {{ default }}
|
||||
alias Type = String
|
||||
end
|
||||
end
|
||||
end
|
||||
18
src/fingers/options/parsers/action_parser.cr
Normal file
18
src/fingers/options/parsers/action_parser.cr
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
module Fingers::Options::Parsers::ActionParser
|
||||
def valid?(value : String) : Tuple(Bool, String)
|
||||
if value.starts_with?(":") && value.ends_with?(":")
|
||||
validate_action(value)
|
||||
else
|
||||
# allow arbirtary shell commands
|
||||
{ true, "ok" }
|
||||
end
|
||||
end
|
||||
|
||||
def validate_action(value)
|
||||
if Fingers::ACTIONS.includes?(value)
|
||||
{ true, "ok" }
|
||||
else
|
||||
{ false, "Invalid action '#{value}'. Possible actions are: #{Fingers::ACTIONS.join(", ")}" }
|
||||
end
|
||||
end
|
||||
end
|
||||
5
src/fingers/options/parsers/bool_parser.cr
Normal file
5
src/fingers/options/parsers/bool_parser.cr
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
module Fingers::Options::Parsers::BoolParser
|
||||
def parse(value : String, _option_name : String) : Bool
|
||||
value == "1" || value.downcase == "true"
|
||||
end
|
||||
end
|
||||
18
src/fingers/options/parsers/enum_parser.cr
Normal file
18
src/fingers/options/parsers/enum_parser.cr
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
module Fingers::Options::Parsers::EnumParser
|
||||
def valid?(value : String) : Tuple(Bool, String)
|
||||
if possible_values_as_strings.includes?(value.to_s)
|
||||
{ true, "ok" }
|
||||
else
|
||||
{ false, "Invalid value '#{value}'. Possible values are: #{possible_values.join(", ")}" }
|
||||
end
|
||||
end
|
||||
|
||||
def possible_values
|
||||
[] of String
|
||||
end
|
||||
|
||||
def possible_values_as_strings
|
||||
possible_values.map { |value| value.to_s }
|
||||
end
|
||||
end
|
||||
|
||||
19
src/fingers/options/parsers/multi_enum_parser.cr
Normal file
19
src/fingers/options/parsers/multi_enum_parser.cr
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
module Fingers::Options::Parsers::MultiEnumParser
|
||||
def valid?(value : String) : Tuple(Bool, String)
|
||||
invalid = value.split(",").reject { |name| possible_values_as_strings.includes?(name) }
|
||||
|
||||
if invalid.empty?
|
||||
{ true, "ok" }
|
||||
else
|
||||
{ false, "Invalid value(s) '#{invalid.join(",")}'. Possible values are: #{possible_values.join(", ")}" }
|
||||
end
|
||||
end
|
||||
|
||||
def possible_values
|
||||
[] of String
|
||||
end
|
||||
|
||||
def possible_values_as_strings
|
||||
possible_values.map { |value| value.to_s }
|
||||
end
|
||||
end
|
||||
17
src/fingers/options/parsers/style_parser.cr
Normal file
17
src/fingers/options/parsers/style_parser.cr
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
module Fingers::Options::Parsers::StyleParser
|
||||
def valid?(value : String) : Tuple(Bool, String)
|
||||
return { true, "ok" } if value.empty?
|
||||
|
||||
begin
|
||||
Tmux.style_printer.print(value)
|
||||
{ true, "ok" }
|
||||
rescue e: ::TmuxStylePrinter::InvalidFormat
|
||||
{ false, "Invalid style format '#{e}'" }
|
||||
end
|
||||
end
|
||||
|
||||
def process(value : String) : String
|
||||
return "" if value.empty?
|
||||
Tmux.style_printer.print(value)
|
||||
end
|
||||
end
|
||||
34
src/fingers/options/patterns.cr
Normal file
34
src/fingers/options/patterns.cr
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
require "./base"
|
||||
|
||||
module Fingers::Options
|
||||
class Patterns < Base
|
||||
alias Type = Hash(String, String)
|
||||
DEFAULT = {} of String => String
|
||||
|
||||
def parse(raw_value : String, option_name : String)
|
||||
val = {} of String => String
|
||||
val[option_name.gsub(/^pattern[-_]/, "")] = raw_value
|
||||
val
|
||||
end
|
||||
|
||||
def valid?(raw_value : String) : Tuple(Bool, String)
|
||||
pattern = raw_value
|
||||
|
||||
begin
|
||||
Regex.new(pattern)
|
||||
{ true, "ok" }
|
||||
rescue e: ArgumentError
|
||||
{ false, e.message || "Could not parse pattern" }
|
||||
end
|
||||
end
|
||||
|
||||
def ==(other : String)
|
||||
other.match(/^pattern/)
|
||||
end
|
||||
|
||||
def write(value : Type, config : Fingers::Config)
|
||||
config.patterns.merge!(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -13,20 +13,28 @@ module Fingers
|
|||
@output : Printer
|
||||
@original_pane : Tmux::Pane
|
||||
@tmux : Tmux
|
||||
@mode : String
|
||||
|
||||
def initialize(
|
||||
@hinter,
|
||||
@output,
|
||||
@original_pane,
|
||||
@state,
|
||||
@tmux
|
||||
@tmux,
|
||||
@mode,
|
||||
)
|
||||
end
|
||||
|
||||
def render
|
||||
clear_screen
|
||||
hide_cursor
|
||||
hinter.run
|
||||
|
||||
begin
|
||||
hinter.run
|
||||
rescue e
|
||||
Log.fatal { e }
|
||||
request_exit!
|
||||
end
|
||||
end
|
||||
|
||||
def process_input(input : String)
|
||||
|
|
@ -46,17 +54,6 @@ module Fingers
|
|||
end
|
||||
end
|
||||
|
||||
def run_action
|
||||
ActionRunner.new(
|
||||
hint: state.input,
|
||||
modifier: state.modifier,
|
||||
match: state.result,
|
||||
original_pane: original_pane
|
||||
).run
|
||||
|
||||
tmux.display_message("Copied: #{state.result}", 1000) unless state.result.empty?
|
||||
end
|
||||
|
||||
private def hide_cursor
|
||||
output.print HIDE_CURSOR_SEQ
|
||||
end
|
||||
|
|
@ -68,13 +65,19 @@ module Fingers
|
|||
private def process_hint(char, modifier)
|
||||
state.input += char
|
||||
state.modifier = modifier
|
||||
|
||||
match = hinter.lookup(state.input)
|
||||
|
||||
handle_match(match) if match
|
||||
tmux.display_message(state.input, 300)
|
||||
if match.nil?
|
||||
render
|
||||
else
|
||||
handle_match(match.not_nil!.text)
|
||||
end
|
||||
end
|
||||
|
||||
private def process_multimode
|
||||
return if mode == "jump"
|
||||
|
||||
prev_state = state.multi_mode
|
||||
state.multi_mode = !state.multi_mode
|
||||
current_state = state.multi_mode
|
||||
|
|
@ -85,7 +88,7 @@ module Fingers
|
|||
end
|
||||
end
|
||||
|
||||
private getter :output, :hinter, :original_pane, :state, :tmux
|
||||
private getter :output, :hinter, :original_pane, :state, :tmux, :mode
|
||||
|
||||
private def handle_match(match)
|
||||
if state.multi_mode
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
require "./priority_queue"
|
||||
|
||||
class HuffmanNode
|
||||
struct HuffmanNode
|
||||
def initialize(weight : Int32, children : Array(HuffmanNode))
|
||||
@weight = weight
|
||||
@children = children
|
||||
|
|
|
|||
49
src/tmux.cr
49
src/tmux.cr
|
|
@ -35,9 +35,6 @@ def build_tmux_format(hash)
|
|||
"{#{fields.join(",")}}"
|
||||
end
|
||||
|
||||
# TODO maybe use system everywhere?
|
||||
|
||||
# rubocop:disable Metrics/ClassLength
|
||||
class Tmux
|
||||
class Shell
|
||||
def initialize
|
||||
|
|
@ -111,6 +108,10 @@ class Tmux
|
|||
@panes : Array(Pane) | Nil
|
||||
@version : SemanticVersion
|
||||
|
||||
def self.style_printer
|
||||
@@style_printer ||= TmuxStylePrinter.new
|
||||
end
|
||||
|
||||
def self.tmux_version_to_semver(version_string)
|
||||
match = version_string.match(/(?<major>[1-9]+[0-9]*)\.(?<minor>[0-9]+)(?<patch_letter>[a-z]+)?/)
|
||||
|
||||
|
|
@ -134,8 +135,20 @@ class Tmux
|
|||
@version = Tmux.tmux_version_to_semver(version_string)
|
||||
end
|
||||
|
||||
def panes : Array(Pane)
|
||||
exec("list-panes -a -F '#{PANE_FORMAT}'").chomp.split("\n").map do |pane|
|
||||
def list_panes(filters = "", target = nil) : Array(Pane)
|
||||
args = ["list-panes", "-F", PANE_FORMAT]
|
||||
|
||||
if target.nil?
|
||||
args << "-a"
|
||||
else
|
||||
args.concat(["-t", target])
|
||||
end
|
||||
|
||||
if !filters.empty?
|
||||
args.concat(["-f", filters])
|
||||
end
|
||||
|
||||
exec(Process.quote(args)).chomp.split("\n").map do |pane|
|
||||
Pane.from_json(pane)
|
||||
end
|
||||
end
|
||||
|
|
@ -148,20 +161,20 @@ class Tmux
|
|||
Pane.from_json(output)
|
||||
end
|
||||
|
||||
def capture_pane(pane : Pane)
|
||||
def capture_pane(pane : Pane, join = true)
|
||||
if pane.pane_in_mode && !pane.scroll_position.nil?
|
||||
scroll_position = pane.scroll_position.not_nil!
|
||||
start_line = -scroll_position.to_i
|
||||
end_line = pane.pane_height.to_i - scroll_position.to_i - 1
|
||||
|
||||
exec("capture-pane -J -p -t '#{pane.pane_id}' -S #{start_line} -E #{end_line}").chomp
|
||||
exec("capture-pane #{join ? "-J" : ""} -p -t '#{pane.pane_id}' -S #{start_line} -E #{end_line}").chomp
|
||||
else
|
||||
exec("capture-pane -J -p -t '#{pane.pane_id}'").chomp
|
||||
exec("capture-pane #{join ? "-J" : ""} -p -t '#{pane.pane_id}'").chomp
|
||||
end
|
||||
end
|
||||
|
||||
def create_window(name, cmd, _pane_width, _pane_height)
|
||||
output = exec("new-window -P -d -n '#{name}' -F '#{WINDOW_FORMAT}' '#{cmd}'").chomp
|
||||
output = exec("new-window -c '\#{pane_current_path}' -P -d -n '#{name}' -F '#{WINDOW_FORMAT}' '#{cmd}'").chomp
|
||||
|
||||
Window.from_json(output)
|
||||
end
|
||||
|
|
@ -209,17 +222,17 @@ class Tmux
|
|||
end
|
||||
|
||||
def set_global_option(name, value)
|
||||
exec(["set-option", "-g", name, value].join(' '))
|
||||
exec(Process.quote(["set-option", "-g", name, value]))
|
||||
end
|
||||
|
||||
def get_global_option(name)
|
||||
exec(["show", "-gqv", name].join(' ')).chomp
|
||||
end
|
||||
|
||||
def set_buffer(value)
|
||||
def set_buffer(value, use_system_clipboard = true)
|
||||
return unless value
|
||||
|
||||
if @version >= Tmux.tmux_version_to_semver("3.2")
|
||||
if @version >= Tmux.tmux_version_to_semver("3.2") && use_system_clipboard
|
||||
args = ["load-buffer", "-w", "-"]
|
||||
else
|
||||
args = ["load-buffer", "-"]
|
||||
|
|
@ -235,7 +248,7 @@ class Tmux
|
|||
)
|
||||
|
||||
cmd.input.print(value)
|
||||
cmd.input.flush
|
||||
cmd.input.close
|
||||
|
||||
cmd.wait
|
||||
|
||||
|
|
@ -243,7 +256,13 @@ class Tmux
|
|||
end
|
||||
|
||||
def select_pane(id)
|
||||
exec(["select-pane", "-t", id].join(' '))
|
||||
args = ["select-pane", "-t", id]
|
||||
|
||||
if @version >= Tmux.tmux_version_to_semver("3.1")
|
||||
args << "-Z"
|
||||
end
|
||||
|
||||
exec(args.join(' '))
|
||||
end
|
||||
|
||||
def zoom_pane(id)
|
||||
|
|
@ -283,7 +302,7 @@ class Tmux
|
|||
exec(Process.quote(["display-message", "-d", delay.to_s, msg]))
|
||||
end
|
||||
|
||||
private def exec(cmd)
|
||||
def exec(cmd)
|
||||
@sh.exec("#{tmux} #{cmd}")
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,10 +3,6 @@ class TmuxStylePrinter
|
|||
class InvalidFormat < Exception
|
||||
end
|
||||
|
||||
abstract class Shell
|
||||
abstract def exec(cmd)
|
||||
end
|
||||
|
||||
STYLE_SEPARATOR = /[ ,]+/
|
||||
|
||||
COLOR_MAP = {
|
||||
|
|
@ -20,32 +16,23 @@ class TmuxStylePrinter
|
|||
white: 7,
|
||||
}
|
||||
|
||||
LAYER_MAP = {
|
||||
bg: "setab",
|
||||
fg: "setaf",
|
||||
LAYER_CODE = {
|
||||
"bg" => 48,
|
||||
"fg" => 38,
|
||||
}
|
||||
|
||||
STYLE_MAP = {
|
||||
bright: "bold",
|
||||
bold: "bold",
|
||||
dim: "dim",
|
||||
underscore: "smul",
|
||||
reverse: "rev",
|
||||
italics: "sitm",
|
||||
"bright" => "\e[1m",
|
||||
"bold" => "\e[1m",
|
||||
"dim" => "\e[2m",
|
||||
"underscore" => "\e[4m",
|
||||
"reverse" => "\e[7m",
|
||||
"italics" => "\e[3m",
|
||||
}
|
||||
|
||||
class ShellExec < Shell
|
||||
def exec(cmd)
|
||||
`#{cmd}`.chomp
|
||||
end
|
||||
end
|
||||
|
||||
@shell : Shell
|
||||
@applied_styles : Hash(String, String)
|
||||
@reset_sequence : String | Nil
|
||||
|
||||
def initialize(shell = ShellExec.new)
|
||||
@shell = shell
|
||||
def initialize
|
||||
@applied_styles = {} of String => String
|
||||
end
|
||||
|
||||
|
|
@ -58,7 +45,7 @@ class TmuxStylePrinter
|
|||
output += parse_style_definition(style)
|
||||
end
|
||||
|
||||
output += reset_sequence if reset_styles_after && !@applied_styles.empty?
|
||||
output += Fingers::ANSI_RESET if reset_styles_after && !@applied_styles.empty?
|
||||
|
||||
output
|
||||
end
|
||||
|
|
@ -89,7 +76,7 @@ class TmuxStylePrinter
|
|||
|
||||
raise InvalidFormat.new("Invalid color definition: #{style}") if color_to_apply.nil?
|
||||
|
||||
result = shell.exec("tput #{LAYER_MAP[layer]} #{color_to_apply}")
|
||||
result = "\e[#{LAYER_CODE[layer]};5;#{color_to_apply}m"
|
||||
|
||||
@applied_styles[layer] = result
|
||||
|
||||
|
|
@ -108,7 +95,7 @@ class TmuxStylePrinter
|
|||
|
||||
raise InvalidFormat.new("Invalid style definition: #{style}") if style_to_apply.nil?
|
||||
|
||||
result = style == "dim" ? "\033[2m" : shell.exec("tput #{STYLE_MAP[style]}")
|
||||
result = style_to_apply
|
||||
|
||||
if should_remove_style
|
||||
@applied_styles.delete(style)
|
||||
|
|
@ -121,14 +108,6 @@ class TmuxStylePrinter
|
|||
end
|
||||
|
||||
private def reset_to_applied_styles!
|
||||
[reset_sequence, @applied_styles.values].join
|
||||
end
|
||||
|
||||
private def reset_sequence
|
||||
@reset_sequence ||= shell.exec("tput sgr0").chomp
|
||||
end
|
||||
|
||||
private def shell
|
||||
@shell
|
||||
[Fingers::ANSI_RESET, @applied_styles.values].join
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ fi
|
|||
CURRENT_FINGERS_VERSION="$($FINGERS_BINARY version)"
|
||||
|
||||
pushd $CURRENT_DIR &> /dev/null
|
||||
CURRENT_GIT_VERSION=$(git describe --tags | sed "s/-.*//g")
|
||||
CURRENT_GIT_VERSION=$(cat shard.yml | grep "^version" | cut -f2 -d':' | sed "s/ //g")
|
||||
popd &> /dev/null
|
||||
|
||||
SKIP_WIZARD=$(tmux show-option -gqv @fingers-skip-wizard)
|
||||
|
|
@ -31,5 +31,13 @@ if [ "$SKIP_WIZARD" = "0" ] && [ "$CURRENT_FINGERS_VERSION" != "$CURRENT_GIT_VER
|
|||
fi
|
||||
fi
|
||||
|
||||
tmux run "$FINGERS_BINARY load-config"
|
||||
if [[ "$TERM" == "dumb" ]]; then
|
||||
# force term value to get proper colors in systemd and tmux 3.6a
|
||||
# https://github.com/Morantron/tmux-fingers/issues/143
|
||||
FINGERS_TERM=$(tmux show-option -gqv default-terminal)
|
||||
else
|
||||
FINGERS_TERM="$TERM"
|
||||
fi
|
||||
|
||||
tmux run "TERM=$FINGERS_TERM $FINGERS_BINARY load-config"
|
||||
exit $?
|
||||
|
|
|
|||
Loading…
Reference in a new issue