Compare commits
44 Commits
Author | SHA1 | Date |
---|---|---|
Jorge Morante | 9b393e5757 | |
Jorge Morante | 27c5512bc2 | |
Jorge Morante | 2996ef2644 | |
Jorge Morante | 3c7c6a8673 | |
Morantron | 03f0048468 | |
Avimitin | 7e693d40cd | |
Jorge Morante | 4d3fa1eb30 | |
Jorge Morante | 47d986b8a2 | |
Jorge Morante | f6813b8b16 | |
Jorge Morante | 4ba2ec3125 | |
Jorge Morante | a30f2fb566 | |
Jorge Morante | 65a3dd2665 | |
Jorge Morante | c186efa73c | |
Jorge Morante | cd3241ad42 | |
Jorge Morante | 05a9ece2d9 | |
Jorge Morante | 1be65bc1dc | |
Jorge Morante | 26d1f67aa9 | |
Jorge Morante | 7e408344b2 | |
Jorge Morante | 680d801770 | |
Jorge Morante | 8cfc2bd1ad | |
Jorge Morante | 6718322da9 | |
Jorge Morante | 27ffac083d | |
Jorge Morante | eb3c21eae6 | |
Jorge Morante | f079539a65 | |
Jorge Morante | cda4b68f8c | |
Jorge Morante | 016c1fa8da | |
Jorge Morante | d6ae53daac | |
Jorge Morante | 13c46d71eb | |
Jorge Morante | 65af29eba9 | |
Jorge Morante | da3cd9f267 | |
Jorge Morante | 5a891fde67 | |
Jorge Morante | 2bb5bd2d5b | |
Jorge Morante | fcc4a3ad6c | |
Jorge Morante | b154d4800f | |
Jorge Morante | 6ad4c5530e | |
Jorge Morante | a47a9b2809 | |
Jorge Morante | 55c47402b3 | |
Jorge Morante | abca481d49 | |
Jorge Morante | 3ef6184fde | |
Morantron | cb64f9b859 | |
Brett Bender | 193d01601f | |
Brett Bender | b2f7567d0a | |
Jorge Morante | c9c6d8e79b | |
Jorge Morante | d65a73fb33 |
40
CHANGELOG.md
40
CHANGELOG.md
|
@ -1,3 +1,43 @@
|
|||
## 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 ).
|
||||
|
|
18
README.md
18
README.md
|
@ -74,6 +74,7 @@ $ tmux source-file ~/.tmux.conf
|
|||
NOTE: for changes to take effect, you'll need to source again your `.tmux.conf` file.
|
||||
|
||||
* [@fingers-key](#fingers-key)
|
||||
* [@fingers-jump-key](#fingers-jump-key)
|
||||
* [@fingers-patterns-N](#fingers-patterns-N)
|
||||
* [@fingers-main-action](#fingers-main-action)
|
||||
* [@fingers-ctrl-action](#fingers-ctrl-action)
|
||||
|
@ -86,12 +87,13 @@ 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-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:
|
||||
|
||||
|
@ -99,6 +101,14 @@ For example:
|
|||
set -g @fingers-key F
|
||||
```
|
||||
|
||||
## @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-patterns-N
|
||||
|
||||
You can also add additional patterns if you want more stuff to be highlighted:
|
||||
|
@ -233,6 +243,12 @@ 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.
|
||||
|
||||
# Acknowledgements and inspiration
|
||||
|
||||
This plugin is heavily inspired by
|
||||
|
|
|
@ -10,4 +10,11 @@ tmux send-keys Enter
|
|||
|
||||
sleep 5
|
||||
|
||||
tmux new-window 'hyperfine --warmup 5 "bin/tmux-fingers start %0 self" --export-markdown /tmp/benchmark-output'
|
||||
echo "Running benchmarks ..."
|
||||
tmux new-window 'hyperfine --warmup 5 --runs 1000 "bin/tmux-fingers start %0 self" --export-markdown /tmp/benchmark-output'
|
||||
|
||||
echo "Waiting for results ..."
|
||||
|
||||
timeout 60 bash -c 'while [[ ! -s /tmp/benchmark-output ]]; do sleep 1; done'
|
||||
|
||||
cat /tmp/benchmark-output
|
||||
|
|
|
@ -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"
|
|
@ -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=$?
|
||||
|
@ -58,7 +57,11 @@ function install_with_brew() {
|
|||
|
||||
function download_binary() {
|
||||
mkdir -p $CURRENT_DIR/bin
|
||||
# TODO check architecture
|
||||
|
||||
if [[ ! "$(uname -m)" == "x86_64" ]]; then
|
||||
echo "tmux-fingers binaries are only provided for x86_64 architecture."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Getting latest release..."
|
||||
|
||||
|
|
|
@ -1,6 +1,2 @@
|
|||
version: 2.0
|
||||
shards:
|
||||
ameba:
|
||||
git: https://github.com/crystal-ameba/ameba.git
|
||||
version: 1.3.1
|
||||
|
||||
shards: {}
|
||||
|
|
16
shard.yml
16
shard.yml
|
@ -1,18 +1,10 @@
|
|||
---
|
||||
version: 2.1.5
|
||||
name: fingers
|
||||
version: 2.0.3
|
||||
|
||||
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
|
||||
|
||||
crystal: 1.11.2
|
||||
license: MIT
|
||||
|
|
|
@ -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
|
||||
|
@ -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::Config::DEFAULT_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::Config::DEFAULT_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
|
||||
|
|
|
@ -4,7 +4,7 @@ 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)
|
||||
end
|
||||
|
||||
def run
|
||||
|
@ -28,9 +28,10 @@ module Fingers
|
|||
cmd.input.flush
|
||||
end
|
||||
|
||||
private getter :match, :modifier, :hint, :original_pane
|
||||
private getter :match, :modifier, :hint, :original_pane, :offset, :mode
|
||||
|
||||
def final_shell_command
|
||||
return jump if mode == "jump"
|
||||
return @final_shell_command if @final_shell_command
|
||||
|
||||
@final_shell_command = case action
|
||||
|
@ -59,6 +60,17 @@ 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"
|
||||
end
|
||||
|
@ -124,9 +136,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
|
||||
|
|
|
@ -9,7 +9,7 @@ class Fingers::Commands::LoadConfig < Fingers::Commands::Base
|
|||
|
||||
property config : Fingers::Config
|
||||
|
||||
DISALLOWED_CHARS = /cimqn/
|
||||
DISALLOWED_CHARS = /[cimqn]/
|
||||
|
||||
def initialize(*args)
|
||||
super(*args)
|
||||
|
@ -38,6 +38,8 @@ class Fingers::Commands::LoadConfig < Fingers::Commands::Base
|
|||
case option
|
||||
when "key"
|
||||
config.key = value
|
||||
when "jump_key"
|
||||
config.jump_key = value
|
||||
when "keyboard_layout"
|
||||
config.keyboard_layout = value
|
||||
when "main_action"
|
||||
|
@ -62,6 +64,8 @@ class Fingers::Commands::LoadConfig < Fingers::Commands::Base
|
|||
config.backdrop_style = tmux.parse_style(value)
|
||||
when "selected_highlight_style"
|
||||
config.selected_highlight_style = tmux.parse_style(value)
|
||||
when "show_copied_notification"
|
||||
config.show_copied_notification = value
|
||||
end
|
||||
|
||||
if option.match(/pattern/)
|
||||
|
@ -71,8 +75,8 @@ class Fingers::Commands::LoadConfig < Fingers::Commands::Base
|
|||
end
|
||||
|
||||
config.patterns = clean_up_patterns([
|
||||
*enabled_default_patterns,
|
||||
*user_defined_patterns,
|
||||
*enabled_default_patterns,
|
||||
])
|
||||
|
||||
config.alphabet = ::Fingers::Config::ALPHABET_MAP[Fingers.config.keyboard_layout].split("").reject do |char|
|
||||
|
@ -93,7 +97,7 @@ class Fingers::Commands::LoadConfig < Fingers::Commands::Base
|
|||
|
||||
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"`
|
||||
`tmux bind-key #{Fingers.config.jump_key} run-shell -b "#{cli} start "\#{pane_id}" jump >>#{Fingers::Dirs::LOG_PATH} 2>&1"`
|
||||
setup_fingers_mode_bindings
|
||||
end
|
||||
|
||||
|
|
|
@ -26,9 +26,11 @@ module Fingers::Commands
|
|||
|
||||
class Start < Base
|
||||
@original_options : Hash(String, String) = {} of String => String
|
||||
@last_pane_id : String | Nil
|
||||
|
||||
def run
|
||||
track_options_to_restore!
|
||||
track_options_to_restore
|
||||
track_last_pane
|
||||
show_hints
|
||||
|
||||
if Fingers.config.benchmark_mode == "1"
|
||||
|
@ -40,7 +42,7 @@ module Fingers::Commands
|
|||
teardown
|
||||
end
|
||||
|
||||
private def track_options_to_restore!
|
||||
private def track_options_to_restore
|
||||
options_to_preserve.each do |option|
|
||||
value = tmux.get_global_option(option)
|
||||
@original_options[option] = value
|
||||
|
@ -53,6 +55,16 @@ module Fingers::Commands
|
|||
end
|
||||
end
|
||||
|
||||
private def restore_last_pane
|
||||
tmux.select_pane(@last_pane_id)
|
||||
tmux.select_pane(target_pane.pane_id)
|
||||
end
|
||||
|
||||
private def track_last_pane
|
||||
last_pane_id = tmux.exec("display-message -t '{last}' -p '\#{pane_id}'").chomp
|
||||
@last_pane_id = last_pane_id unless last_pane_id.empty?
|
||||
end
|
||||
|
||||
private def options_to_preserve
|
||||
%w[prefix prefix2]
|
||||
end
|
||||
|
@ -94,7 +106,9 @@ module Fingers::Commands
|
|||
tmux.swap_panes(fingers_pane_id, target_pane.pane_id)
|
||||
tmux.kill_pane(fingers_pane_id)
|
||||
|
||||
restore_last_pane
|
||||
restore_options
|
||||
|
||||
view.run_action if state.result
|
||||
end
|
||||
|
||||
|
@ -102,6 +116,10 @@ module Fingers::Commands
|
|||
tmux.find_pane_by_id(@args[0]).not_nil!
|
||||
end
|
||||
|
||||
private getter mode : String do
|
||||
@args[1].not_nil!
|
||||
end
|
||||
|
||||
private getter fingers_window : Tmux::Window do
|
||||
tmux.create_window("[fingers]", "cat", 80, 24)
|
||||
end
|
||||
|
@ -123,12 +141,13 @@ module Fingers::Commands
|
|||
input: pane_contents,
|
||||
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,7 +156,8 @@ module Fingers::Commands
|
|||
state: state,
|
||||
output: pane_printer,
|
||||
original_pane: target_pane,
|
||||
tmux: tmux
|
||||
tmux: tmux,
|
||||
mode: mode
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ module Fingers
|
|||
include JSON::Serializable
|
||||
|
||||
property key : String
|
||||
property jump_key : String
|
||||
property keyboard_layout : String
|
||||
property patterns : Array(String)
|
||||
property alphabet : Array(String)
|
||||
|
@ -20,6 +21,7 @@ module Fingers
|
|||
property selected_highlight_style : String
|
||||
property backdrop_style : String
|
||||
property tmux_version : String
|
||||
property show_copied_notification : String
|
||||
|
||||
FORMAT_PRINTER = TmuxStylePrinter.new
|
||||
|
||||
|
@ -62,6 +64,7 @@ module Fingers
|
|||
|
||||
def initialize(
|
||||
@key = "F",
|
||||
@jump_key = "J",
|
||||
@keyboard_layout = "qwerty",
|
||||
@alphabet = [] of String,
|
||||
@patterns = [] of String,
|
||||
|
@ -76,6 +79,7 @@ module Fingers
|
|||
@selected_highlight_style = FORMAT_PRINTER.print("fg=blue"),
|
||||
@backdrop_style = "",
|
||||
@tmux_version = "",
|
||||
@show_copied_notification = "0",
|
||||
@benchmark_mode = "0"
|
||||
)
|
||||
end
|
||||
|
|
|
@ -5,9 +5,10 @@ require "file_utils"
|
|||
module Fingers::Dirs
|
||||
TMUX_PID = (ENV["TMUX"] || ",0000").split(",")[1]
|
||||
|
||||
ROOT = Path["/tmp"] / "tmux-#{TMUX_PID}"
|
||||
TMP = Path[File.dirname(File.tempname)]
|
||||
|
||||
LOG_PATH = ROOT / "fingers.log"
|
||||
ROOT = TMP / "tmux-#{TMUX_PID}"
|
||||
LOG_PATH = TMP / "fingers.log"
|
||||
CACHE = ROOT / "tmux-fingers"
|
||||
CONFIG_PATH = CACHE / "config.json"
|
||||
SOCKET_PATH = CACHE / "fingers.sock"
|
||||
|
|
|
@ -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,14 +71,13 @@ 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, ending)
|
||||
result = line.gsub(pattern) { |_m| replace($~) }
|
||||
def process_line(line, line_index, ending)
|
||||
result = line.gsub(pattern) { |_m| replace($~, line_index) }
|
||||
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)
|
||||
|
@ -86,40 +92,86 @@ 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
|
||||
end
|
||||
absolute_offset = {
|
||||
line_index,
|
||||
match.begin(0) + (relative_capture_offset ? relative_capture_offset[0] : 0)
|
||||
}
|
||||
|
||||
if hints_by_text.has_key?(captured_text)
|
||||
hint = hints_by_text[captured_text]
|
||||
else
|
||||
hint = hints.pop
|
||||
hint = hint_for_text(captured_text)
|
||||
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 +187,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 +208,18 @@ 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
|
||||
|
||||
private property lines : Array(String)
|
||||
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)
|
||||
|
@ -47,14 +55,18 @@ module Fingers
|
|||
end
|
||||
|
||||
def run_action
|
||||
match = hinter.lookup(state.input)
|
||||
|
||||
ActionRunner.new(
|
||||
hint: state.input,
|
||||
modifier: state.modifier,
|
||||
match: state.result,
|
||||
original_pane: original_pane
|
||||
original_pane: original_pane,
|
||||
offset: match ? match.not_nil!.offset : nil,
|
||||
mode: mode
|
||||
).run
|
||||
|
||||
tmux.display_message("Copied: #{state.result}", 1000) unless state.result.empty?
|
||||
tmux.display_message("Copied: #{state.result}", 1000) if should_notify?
|
||||
end
|
||||
|
||||
private def hide_cursor
|
||||
|
@ -68,13 +80,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 +103,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
|
||||
|
@ -102,5 +120,9 @@ module Fingers
|
|||
private def request_exit!
|
||||
state.exiting = true
|
||||
end
|
||||
|
||||
private def should_notify?
|
||||
!state.result.empty? && Fingers.config.show_copied_notification == "1"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require "./priority_queue"
|
||||
|
||||
class HuffmanNode
|
||||
struct HuffmanNode
|
||||
def initialize(weight : Int32, children : Array(HuffmanNode))
|
||||
@weight = weight
|
||||
@children = children
|
||||
|
|
|
@ -148,15 +148,15 @@ 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
|
||||
|
||||
|
@ -283,7 +283,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
|
||||
|
|
Loading…
Reference in New Issue