adding jump mode functionality
This commit is contained in:
parent
b154d4800f
commit
fcc4a3ad6c
|
@ -46,13 +46,6 @@ describe Fingers::Hinter do
|
||||||
alphabet: alphabet,
|
alphabet: alphabet,
|
||||||
output: output,
|
output: output,
|
||||||
)
|
)
|
||||||
|
|
||||||
puts "before"
|
|
||||||
puts input
|
|
||||||
puts "---------"
|
|
||||||
puts "after"
|
|
||||||
hinter.run
|
|
||||||
puts output.contents
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "only highlights captured groups" do
|
it "only highlights captured groups" do
|
||||||
|
@ -89,12 +82,58 @@ Changes not staged for commit:
|
||||||
alphabet: alphabet,
|
alphabet: alphabet,
|
||||||
output: output,
|
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
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,7 +4,7 @@ module Fingers
|
||||||
class ActionRunner
|
class ActionRunner
|
||||||
@final_shell_command : String | Nil
|
@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
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
|
@ -28,9 +28,10 @@ module Fingers
|
||||||
cmd.input.flush
|
cmd.input.flush
|
||||||
end
|
end
|
||||||
|
|
||||||
private getter :match, :modifier, :hint, :original_pane
|
private getter :match, :modifier, :hint, :original_pane, :offset, :mode
|
||||||
|
|
||||||
def final_shell_command
|
def final_shell_command
|
||||||
|
return jump if mode == "jump"
|
||||||
return @final_shell_command if @final_shell_command
|
return @final_shell_command if @final_shell_command
|
||||||
|
|
||||||
@final_shell_command = case action
|
@final_shell_command = case action
|
||||||
|
@ -40,6 +41,8 @@ module Fingers
|
||||||
open
|
open
|
||||||
when ":paste:"
|
when ":paste:"
|
||||||
paste
|
paste
|
||||||
|
when ":jump:"
|
||||||
|
jump
|
||||||
when nil
|
when nil
|
||||||
# do nothing
|
# do nothing
|
||||||
else
|
else
|
||||||
|
@ -59,6 +62,18 @@ module Fingers
|
||||||
system_open_command
|
system_open_command
|
||||||
end
|
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 start-of-line`
|
||||||
|
`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
|
def paste
|
||||||
"tmux paste-buffer"
|
"tmux paste-buffer"
|
||||||
end
|
end
|
||||||
|
|
|
@ -38,6 +38,8 @@ class Fingers::Commands::LoadConfig < Fingers::Commands::Base
|
||||||
case option
|
case option
|
||||||
when "key"
|
when "key"
|
||||||
config.key = value
|
config.key = value
|
||||||
|
when "jump_key"
|
||||||
|
config.jump_key = value
|
||||||
when "keyboard_layout"
|
when "keyboard_layout"
|
||||||
config.keyboard_layout = value
|
config.keyboard_layout = value
|
||||||
when "main_action"
|
when "main_action"
|
||||||
|
@ -95,7 +97,7 @@ class Fingers::Commands::LoadConfig < Fingers::Commands::Base
|
||||||
|
|
||||||
def setup_bindings
|
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 #{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
|
setup_fingers_mode_bindings
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -102,6 +102,10 @@ module Fingers::Commands
|
||||||
tmux.find_pane_by_id(@args[0]).not_nil!
|
tmux.find_pane_by_id(@args[0]).not_nil!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private getter mode : String do
|
||||||
|
@args[1].not_nil!
|
||||||
|
end
|
||||||
|
|
||||||
private getter fingers_window : Tmux::Window do
|
private getter fingers_window : Tmux::Window do
|
||||||
tmux.create_window("[fingers]", "cat", 80, 24)
|
tmux.create_window("[fingers]", "cat", 80, 24)
|
||||||
end
|
end
|
||||||
|
@ -123,7 +127,8 @@ module Fingers::Commands
|
||||||
input: pane_contents,
|
input: pane_contents,
|
||||||
width: target_pane.pane_width.to_i,
|
width: target_pane.pane_width.to_i,
|
||||||
state: state,
|
state: state,
|
||||||
output: pane_printer
|
output: pane_printer,
|
||||||
|
reuse_hints: mode != "jump",
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -137,7 +142,8 @@ module Fingers::Commands
|
||||||
state: state,
|
state: state,
|
||||||
output: pane_printer,
|
output: pane_printer,
|
||||||
original_pane: target_pane,
|
original_pane: target_pane,
|
||||||
tmux: tmux
|
tmux: tmux,
|
||||||
|
mode: mode
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ module Fingers
|
||||||
include JSON::Serializable
|
include JSON::Serializable
|
||||||
|
|
||||||
property key : String
|
property key : String
|
||||||
|
property jump_key : String
|
||||||
property keyboard_layout : String
|
property keyboard_layout : String
|
||||||
property patterns : Array(String)
|
property patterns : Array(String)
|
||||||
property alphabet : Array(String)
|
property alphabet : Array(String)
|
||||||
|
@ -63,6 +64,7 @@ module Fingers
|
||||||
|
|
||||||
def initialize(
|
def initialize(
|
||||||
@key = "F",
|
@key = "F",
|
||||||
|
@jump_key = "J",
|
||||||
@keyboard_layout = "qwerty",
|
@keyboard_layout = "qwerty",
|
||||||
@alphabet = [] of String,
|
@alphabet = [] of String,
|
||||||
@patterns = [] of String,
|
@patterns = [] of String,
|
||||||
|
|
|
@ -4,6 +4,15 @@ require "./match_formatter"
|
||||||
require "./types"
|
require "./types"
|
||||||
|
|
||||||
module Fingers
|
module Fingers
|
||||||
|
struct Target
|
||||||
|
property text : String
|
||||||
|
property hint : String
|
||||||
|
property offset : Tuple(Int32, Int32)
|
||||||
|
|
||||||
|
def initialize(@text, @hint, @offset)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class Hinter
|
class Hinter
|
||||||
@formatter : Formatter
|
@formatter : Formatter
|
||||||
@patterns : Array(String)
|
@patterns : Array(String)
|
||||||
|
@ -11,6 +20,7 @@ module Fingers
|
||||||
@pattern : Regex | Nil
|
@pattern : Regex | Nil
|
||||||
@hints : Array(String) | Nil
|
@hints : Array(String) | Nil
|
||||||
@n_matches : Int32 | Nil
|
@n_matches : Int32 | Nil
|
||||||
|
@reuse_hints : Bool
|
||||||
|
|
||||||
def initialize(
|
def initialize(
|
||||||
input : Array(String),
|
input : Array(String),
|
||||||
|
@ -20,42 +30,39 @@ module Fingers
|
||||||
patterns = Fingers.config.patterns,
|
patterns = Fingers.config.patterns,
|
||||||
alphabet = Fingers.config.alphabet,
|
alphabet = Fingers.config.alphabet,
|
||||||
huffman = Huffman.new,
|
huffman = Huffman.new,
|
||||||
formatter = ::Fingers::MatchFormatter.new
|
formatter = ::Fingers::MatchFormatter.new,
|
||||||
|
reuse_hints = false
|
||||||
)
|
)
|
||||||
@lines = input
|
@lines = input
|
||||||
@width = width
|
@width = width
|
||||||
@hints_by_text = {} of String => String
|
@target_by_hint = {} of String => Target
|
||||||
@lookup_table = {} of String => String
|
@target_by_text = {} of String => Target
|
||||||
@state = state
|
@state = state
|
||||||
@output = output
|
@output = output
|
||||||
@formatter = formatter
|
@formatter = formatter
|
||||||
@huffman = huffman
|
@huffman = huffman
|
||||||
@patterns = patterns
|
@patterns = patterns
|
||||||
@alphabet = alphabet
|
@alphabet = alphabet
|
||||||
|
@reuse_hints = reuse_hints
|
||||||
end
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
lines[0..-2].each { |line| process_line(line, "\n") }
|
regenerate_hints! unless reuse_hints
|
||||||
process_line(lines[-1], "")
|
lines[0..-2].each_with_index { |line, index| process_line(line, index, "\n") }
|
||||||
|
process_line(lines[-1], lines.size - 1, "")
|
||||||
|
|
||||||
# STDOUT.flush
|
|
||||||
output.flush
|
output.flush
|
||||||
|
|
||||||
build_lookup_table!
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def lookup(hint)
|
def lookup(hint) : Target | Nil
|
||||||
lookup_table.fetch(hint) { nil }
|
target_by_hint.fetch(hint) { nil }
|
||||||
end
|
|
||||||
|
|
||||||
def matches
|
|
||||||
@matches ||= @hints_by_text.keys.uniq!.flatten
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# private
|
# private
|
||||||
|
|
||||||
private getter :hints,
|
private getter :hints,
|
||||||
:hints_by_text,
|
:hints_by_text,
|
||||||
|
:offsets_by_hint,
|
||||||
:input,
|
:input,
|
||||||
:lookup_table,
|
:lookup_table,
|
||||||
:width,
|
:width,
|
||||||
|
@ -64,14 +71,13 @@ module Fingers
|
||||||
:huffman,
|
:huffman,
|
||||||
:output,
|
:output,
|
||||||
:patterns,
|
:patterns,
|
||||||
:alphabet
|
:alphabet,
|
||||||
|
:reuse_hints,
|
||||||
|
:target_by_hint,
|
||||||
|
:target_by_text
|
||||||
|
|
||||||
def build_lookup_table!
|
def process_line(line, line_index, ending)
|
||||||
@lookup_table = hints_by_text.invert
|
result = line.gsub(pattern) { |_m| replace($~, line_index) }
|
||||||
end
|
|
||||||
|
|
||||||
def process_line(line, ending)
|
|
||||||
result = line.gsub(pattern) { |_m| replace($~) }
|
|
||||||
result = Fingers.config.backdrop_style + result
|
result = Fingers.config.backdrop_style + result
|
||||||
double_width_correction = ((line.bytesize - line.size) / 3).round.to_i
|
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)
|
||||||
|
@ -86,31 +92,25 @@ module Fingers
|
||||||
def hints : Array(String)
|
def hints : Array(String)
|
||||||
return @hints.as(Array(String)) if !@hints.nil?
|
return @hints.as(Array(String)) if !@hints.nil?
|
||||||
|
|
||||||
@hints = huffman.generate_hints(alphabet: alphabet, n: n_matches)
|
regenerate_hints!
|
||||||
|
|
||||||
|
@hints.as(Array(String))
|
||||||
end
|
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]
|
text = match[0]
|
||||||
|
|
||||||
captured_text = match["match"]? || text
|
captured_text = match["match"]? || text
|
||||||
|
capture_offset = capture_offset_for_match(match, captured_text)
|
||||||
|
|
||||||
if match["match"]?
|
hint = hint_for_text(text)
|
||||||
match_start, match_end = {match.begin(0), match.end(0)}
|
build_target(text, hint, {line_index, match.begin(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
|
|
||||||
|
|
||||||
if hints_by_text.has_key?(captured_text)
|
|
||||||
hint = hints_by_text[captured_text]
|
|
||||||
else
|
|
||||||
hint = hints.pop
|
|
||||||
|
|
||||||
raise "Too many matches" if hint.nil?
|
|
||||||
|
|
||||||
hints_by_text[captured_text] = hint
|
|
||||||
end
|
|
||||||
|
|
||||||
if !state.input.empty? && !hint.starts_with?(state.input)
|
if !state.input.empty? && !hint.starts_with?(state.input)
|
||||||
return text
|
return text
|
||||||
|
@ -124,6 +124,45 @@ module Fingers
|
||||||
)
|
)
|
||||||
end
|
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 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
|
def find_capture_offset(match : Regex::MatchData) : Tuple(Int32, Int32) | Nil
|
||||||
index = capture_indices.find { |i| match[i]? }
|
index = capture_indices.find { |i| match[i]? }
|
||||||
|
|
||||||
|
@ -139,6 +178,14 @@ module Fingers
|
||||||
def n_matches : Int32
|
def n_matches : Int32
|
||||||
return @n_matches.as(Int32) if !@n_matches.nil?
|
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
|
match_set = Set(String).new
|
||||||
|
|
||||||
lines.each do |line|
|
lines.each do |line|
|
||||||
|
@ -152,6 +199,18 @@ module Fingers
|
||||||
match_set.size
|
match_set.size
|
||||||
end
|
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)
|
private property lines : Array(String)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,13 +13,15 @@ module Fingers
|
||||||
@output : Printer
|
@output : Printer
|
||||||
@original_pane : Tmux::Pane
|
@original_pane : Tmux::Pane
|
||||||
@tmux : Tmux
|
@tmux : Tmux
|
||||||
|
@mode : String
|
||||||
|
|
||||||
def initialize(
|
def initialize(
|
||||||
@hinter,
|
@hinter,
|
||||||
@output,
|
@output,
|
||||||
@original_pane,
|
@original_pane,
|
||||||
@state,
|
@state,
|
||||||
@tmux
|
@tmux,
|
||||||
|
@mode
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -47,11 +49,15 @@ module Fingers
|
||||||
end
|
end
|
||||||
|
|
||||||
def run_action
|
def run_action
|
||||||
|
match = hinter.lookup(state.input)
|
||||||
|
|
||||||
ActionRunner.new(
|
ActionRunner.new(
|
||||||
hint: state.input,
|
hint: state.input,
|
||||||
modifier: state.modifier,
|
modifier: state.modifier,
|
||||||
match: state.result,
|
match: state.result,
|
||||||
original_pane: original_pane
|
original_pane: original_pane,
|
||||||
|
offset: match ? match.not_nil!.offset : nil,
|
||||||
|
mode: mode
|
||||||
).run
|
).run
|
||||||
|
|
||||||
tmux.display_message("Copied: #{state.result}", 1000) if should_notify?
|
tmux.display_message("Copied: #{state.result}", 1000) if should_notify?
|
||||||
|
@ -68,12 +74,13 @@ module Fingers
|
||||||
private def process_hint(char, modifier)
|
private def process_hint(char, modifier)
|
||||||
state.input += char
|
state.input += char
|
||||||
state.modifier = modifier
|
state.modifier = modifier
|
||||||
|
|
||||||
match = hinter.lookup(state.input)
|
match = hinter.lookup(state.input)
|
||||||
|
|
||||||
if match
|
if match.nil?
|
||||||
handle_match(match)
|
|
||||||
else
|
|
||||||
render
|
render
|
||||||
|
else
|
||||||
|
handle_match(match.not_nil!.text)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -88,7 +95,7 @@ module Fingers
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private getter :output, :hinter, :original_pane, :state, :tmux
|
private getter :output, :hinter, :original_pane, :state, :tmux, :mode
|
||||||
|
|
||||||
private def handle_match(match)
|
private def handle_match(match)
|
||||||
if state.multi_mode
|
if state.multi_mode
|
||||||
|
|
Loading…
Reference in New Issue