remove ruby
This commit is contained in:
parent
f4188c2cd3
commit
62543f7639
|
@ -1,41 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'logger'
|
||||
require 'json'
|
||||
require 'singleton'
|
||||
require 'timeout'
|
||||
require 'socket'
|
||||
require 'pathname'
|
||||
require 'tmpdir'
|
||||
require 'set'
|
||||
|
||||
# Top level fingers namespace
|
||||
module Fingers
|
||||
end
|
||||
|
||||
require 'tmux'
|
||||
require 'tmux_format_printer'
|
||||
require 'huffman'
|
||||
require 'priority_queue'
|
||||
|
||||
require 'fingers/version'
|
||||
require 'fingers/dirs'
|
||||
require 'fingers/config'
|
||||
|
||||
# commands
|
||||
# TODO dynamically require command?
|
||||
require 'fingers/commands'
|
||||
require 'fingers/commands/base'
|
||||
require 'fingers/commands/check_version'
|
||||
require 'fingers/commands/load_config'
|
||||
require 'fingers/commands/send_input'
|
||||
require 'fingers/commands/start'
|
||||
require 'fingers/commands/trace_start'
|
||||
|
||||
require 'fingers/action_runner'
|
||||
require 'fingers/hinter'
|
||||
require 'fingers/input_socket'
|
||||
require 'fingers/logger'
|
||||
require 'fingers/view'
|
||||
require 'fingers/match_formatter'
|
||||
require 'fingers/cli'
|
|
@ -1,112 +0,0 @@
|
|||
class Fingers::ActionRunner
|
||||
def initialize(modifier:, match:, hint:, original_pane:)
|
||||
@modifier = modifier
|
||||
@match = match
|
||||
@hint = hint
|
||||
@original_pane = original_pane
|
||||
end
|
||||
|
||||
def run
|
||||
Tmux.instance.set_buffer(match)
|
||||
|
||||
return unless final_shell_command
|
||||
|
||||
IO.popen(action_env, final_shell_command, "r+") do |io|
|
||||
io.puts match
|
||||
io.close_write
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_accessor :match, :modifier, :hint, :original_pane
|
||||
|
||||
def final_shell_command
|
||||
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 = prepend_pane_path(@final_shell_command)
|
||||
end
|
||||
|
||||
def prepend_pane_path(cmd)
|
||||
return if (cmd || '').empty?
|
||||
|
||||
"cd #{original_pane.pane_current_path}; #{cmd}"
|
||||
end
|
||||
|
||||
def copy
|
||||
# return unless ENV['DISPLAY']
|
||||
return unless system_copy_command
|
||||
|
||||
system_copy_command
|
||||
end
|
||||
|
||||
def open
|
||||
# return unless ENV['DISPLAY']
|
||||
return unless system_open_command
|
||||
|
||||
system_open_command
|
||||
end
|
||||
|
||||
def paste
|
||||
'tmux paste-buffer'
|
||||
end
|
||||
|
||||
def shell_action
|
||||
action
|
||||
end
|
||||
|
||||
def action_env
|
||||
{ 'MODIFIER' => modifier, 'HINT' => hint }
|
||||
end
|
||||
|
||||
def action
|
||||
@action ||= Fingers.config.get_action(modifier)
|
||||
end
|
||||
|
||||
def system_copy_command
|
||||
@system_copy_command ||= if program_exists?('pbcopy')
|
||||
if program_exists?('reattach-to-user-namespace')
|
||||
'reattach-to-user-namespace'
|
||||
else
|
||||
'pbcopy'
|
||||
end
|
||||
elsif program_exists?('clip.exe')
|
||||
'cat | clip.exe'
|
||||
elsif program_exists?('wl-copy')
|
||||
'wl-copy'
|
||||
elsif program_exists?('xclip')
|
||||
'xclip -selection clipboard'
|
||||
elsif program_exists?('xsel')
|
||||
'xsel -i --clipboard'
|
||||
elsif program_exists?('putclip')
|
||||
'putclip'
|
||||
end
|
||||
end
|
||||
|
||||
def system_open_command
|
||||
@system_open_command ||= if program_exists?('cygstart')
|
||||
'xargs cygstart'
|
||||
elsif program_exists?('xdg-open')
|
||||
'xargs xdg-open'
|
||||
elsif program_exists?('open')
|
||||
'xargs open'
|
||||
end
|
||||
end
|
||||
|
||||
def program_exists?(program)
|
||||
system("which #{program}")
|
||||
end
|
||||
end
|
|
@ -1,37 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
module Fingers
|
||||
class CLI
|
||||
def initialize(args, cli_path)
|
||||
@args = args
|
||||
@cli_path = cli_path
|
||||
end
|
||||
|
||||
def run
|
||||
Fingers.benchmark_stamp('boot:end') if ARGV[0] == 'start'
|
||||
|
||||
command_class = case ARGV[0]
|
||||
when 'start'
|
||||
Fingers::Commands::Start
|
||||
when 'check_version'
|
||||
Fingers::Commands::CheckVersion
|
||||
when 'send_input'
|
||||
Fingers::Commands::SendInput
|
||||
when 'load_config'
|
||||
Fingers::Commands::LoadConfig
|
||||
when 'trace_start'
|
||||
Fingers::Commands::TraceStart
|
||||
else
|
||||
raise "Unknown command #{ARGV[0]}"
|
||||
end
|
||||
|
||||
begin
|
||||
command_class.new(args, cli_path).run
|
||||
rescue StandardError => e
|
||||
Fingers.logger.error(e)
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :args, :cli_path
|
||||
end
|
||||
end
|
|
@ -1,4 +0,0 @@
|
|||
module Fingers
|
||||
module Commands
|
||||
end
|
||||
end
|
|
@ -1,14 +0,0 @@
|
|||
class Fingers::Commands::Base
|
||||
def initialize(args, cli)
|
||||
@args = args
|
||||
@cli = cli
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_reader :args, :cli
|
||||
|
||||
def tmux
|
||||
@tmux ||= ::Tmux.instance
|
||||
end
|
||||
end
|
|
@ -1,19 +0,0 @@
|
|||
class Fingers::Commands::CheckVersion < Fingers::Commands::Base
|
||||
def run
|
||||
require 'net/https'
|
||||
|
||||
puts 'Checking version...'
|
||||
uri = URI('https://api.github.com/repos/morantron/tmux-fingers/tags')
|
||||
|
||||
response = Net::HTTP.get_response(uri)
|
||||
json_response = JSON.parse(response.body)
|
||||
|
||||
latest_release = json_response.map { |tag| Gem::Version.new(tag['name']) }.max
|
||||
|
||||
current_release = Gem::Version.new(Fingers::VERSION)
|
||||
|
||||
puts "There is a new tmux-fingers release: #{latest_release}" if latest_release > current_release
|
||||
rescue StandardError => e
|
||||
puts 'Could not check version'
|
||||
end
|
||||
end
|
|
@ -1,192 +0,0 @@
|
|||
class Fingers::Commands::LoadConfig < Fingers::Commands::Base
|
||||
DISALLOWED_CHARS = /cimqn/.freeze
|
||||
|
||||
FINGERS_FILE_PATH = "#{ENV['HOME']}/.fingersrc"
|
||||
|
||||
DEFAULT_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:///)[^ ()'\"]+)",
|
||||
"path": '(([.\\w\\-~\\$@]+)?(/[.\\w\\-@]+)+/?)'
|
||||
}.freeze
|
||||
|
||||
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'
|
||||
}.freeze
|
||||
|
||||
def run
|
||||
ensure_cache_folder
|
||||
validate_options!
|
||||
parse_tmux_conf
|
||||
setup_bindings
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_tmux_conf
|
||||
options = shell_safe_options
|
||||
|
||||
user_defined_patterns = []
|
||||
|
||||
Fingers.reset_config
|
||||
|
||||
options.each do |pair|
|
||||
option, value = pair
|
||||
|
||||
option = option
|
||||
|
||||
if option.match(/pattern/)
|
||||
user_defined_patterns.push(value)
|
||||
elsif option.match(/format/)
|
||||
parsed_format = Tmux.instance.parse_format(value)
|
||||
|
||||
Fingers.config.send("#{option}=".to_sym, parsed_format)
|
||||
else
|
||||
Fingers.config.send("#{option}=".to_sym, value)
|
||||
end
|
||||
end
|
||||
|
||||
Fingers.config.patterns = clean_up_patterns([
|
||||
*enabled_default_patterns,
|
||||
*user_defined_patterns
|
||||
])
|
||||
|
||||
Fingers.config.alphabet = ALPHABET_MAP[Fingers.config.keyboard_layout.to_sym].split('')
|
||||
|
||||
fingers_file_require_path = File.expand_path(FINGERS_FILE_PATH, __dir__)
|
||||
|
||||
Fingers.logger.debug("Config: #{FINGERS_FILE_PATH}")
|
||||
Fingers.logger.debug("fingers_file_require_path: #{fingers_file_require_path}")
|
||||
|
||||
|
||||
if File.exist?(FINGERS_FILE_PATH)
|
||||
`cp #{FINGERS_FILE_PATH} /tmp/fingersrc.rb`
|
||||
require "/tmp/fingersrc.rb"
|
||||
`rm /tmp/fingersrc.rb`
|
||||
end
|
||||
|
||||
Fingers.save_config
|
||||
end
|
||||
|
||||
def clean_up_patterns(patterns)
|
||||
patterns.reject(&:empty?)
|
||||
end
|
||||
|
||||
def setup_bindings
|
||||
input_mode = 'fingers-mode'
|
||||
ruby_bin = "#{RbConfig.ruby} --disable-gems"
|
||||
|
||||
`tmux bind-key #{Fingers.config.key} run-shell -b "#{ruby_bin} #{cli} start '#{input_mode}' '\#{pane_id}' self >>#{Fingers::Dirs::LOG_PATH} 2>&1"`
|
||||
`tmux bind-key O run-shell -b "#{ruby_bin} #{cli} start '#{input_mode}' '\#{pane_id}' other >>#{Fingers::Dirs::LOG_PATH} 2>&1"`
|
||||
|
||||
setup_fingers_mode_bindings if input_mode == 'fingers-mode'
|
||||
end
|
||||
|
||||
def setup_fingers_mode_bindings
|
||||
('a'..'z').to_a.each do |char|
|
||||
next if char.match(DISALLOWED_CHARS)
|
||||
|
||||
fingers_mode_bind(char, "hint:#{char}:main")
|
||||
fingers_mode_bind(char.upcase, "hint:#{char}:shift")
|
||||
fingers_mode_bind("C-#{char}", "hint:#{char}:ctrl")
|
||||
fingers_mode_bind("M-#{char}", "hint:#{char}:alt")
|
||||
end
|
||||
|
||||
fingers_mode_bind('Space', 'fzf')
|
||||
fingers_mode_bind('C-c', 'exit')
|
||||
fingers_mode_bind('q', 'exit')
|
||||
fingers_mode_bind('Escape', 'exit')
|
||||
|
||||
fingers_mode_bind('?', 'toggle-help')
|
||||
|
||||
fingers_mode_bind('Enter', 'noop')
|
||||
fingers_mode_bind('Tab', 'toggle_multi_mode')
|
||||
|
||||
fingers_mode_bind('Any', 'noop')
|
||||
end
|
||||
|
||||
def enabled_default_patterns
|
||||
DEFAULT_PATTERNS.values
|
||||
end
|
||||
|
||||
def to_bool(input)
|
||||
input == '1'
|
||||
end
|
||||
|
||||
def shell_safe_options
|
||||
options = {}
|
||||
|
||||
fingers_options_names.each do |option|
|
||||
option_method = option_to_method(option)
|
||||
|
||||
options[option_method] = `tmux show-option -gv #{option}`.chomp
|
||||
end
|
||||
|
||||
options
|
||||
end
|
||||
|
||||
def valid_option?(option)
|
||||
option_method = option_to_method(option)
|
||||
|
||||
Fingers.config.respond_to?(option_method) || option.match(/^@fingers-pattern-\d+$/)
|
||||
end
|
||||
|
||||
def ensure_cache_folder
|
||||
require 'fileutils'
|
||||
FileUtils.mkdir_p(Fingers::Dirs::CACHE) unless File.exist?(Fingers::Dirs::CACHE)
|
||||
end
|
||||
|
||||
def fingers_options_names
|
||||
@fingers_options_names ||= `tmux show-options -g | grep ^@fingers`.split("\n").map { |line| line.split(' ')[0] }
|
||||
end
|
||||
|
||||
def unset_tmux_option!(option)
|
||||
`tmux set-option -ug #{option}`
|
||||
end
|
||||
|
||||
def validate_options!
|
||||
errors = []
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
def option_to_method(option)
|
||||
option.gsub(/^@fingers-/, '').tr('-', '_')
|
||||
end
|
||||
|
||||
def fingers_mode_bind(key, command)
|
||||
`tmux bind-key -Tfingers "#{key}" run-shell -b "#{cli} send_input #{command}"`
|
||||
end
|
||||
end
|
|
@ -1,7 +0,0 @@
|
|||
class Fingers::Commands::SendInput < Fingers::Commands::Base
|
||||
def run
|
||||
socket = InputSocket.new
|
||||
|
||||
socket.send_message(args[1])
|
||||
end
|
||||
end
|
|
@ -1,184 +0,0 @@
|
|||
class PanePrinter
|
||||
def initialize(pane_tty)
|
||||
@pane_tty = pane_tty
|
||||
@buf = ''
|
||||
@file = File.open(@pane_tty, 'w')
|
||||
end
|
||||
|
||||
def print(msg)
|
||||
@file.print(msg)
|
||||
#@buf += msg
|
||||
end
|
||||
|
||||
def flush
|
||||
#@file.print(@buf)
|
||||
end
|
||||
end
|
||||
|
||||
class Fingers::Commands::Start < Fingers::Commands::Base
|
||||
State = Struct.new(
|
||||
:show_help,
|
||||
:multi_mode,
|
||||
:input,
|
||||
:modifier,
|
||||
:selected_hints,
|
||||
:selected_matches,
|
||||
:multi_matches,
|
||||
:result,
|
||||
:exiting
|
||||
)
|
||||
|
||||
def run
|
||||
_, _input_mode, from_pane_id, target = args
|
||||
|
||||
Fingers.logger.debug("from_pane_id: #{from_pane_id}")
|
||||
@from_pane_id = from_pane_id
|
||||
@target = target
|
||||
|
||||
create_window!
|
||||
track_options_to_restore!
|
||||
|
||||
show_hints
|
||||
handle_input
|
||||
teardown
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :from_pane_id, :target
|
||||
|
||||
def create_window!
|
||||
fingers_window
|
||||
|
||||
tmux.resize_window(
|
||||
fingers_window.window_id,
|
||||
target_pane.pane_width.to_i,
|
||||
target_pane.pane_height.to_i
|
||||
)
|
||||
end
|
||||
|
||||
def fingers_window
|
||||
@fingers_window ||= tmux.create_window('[fingers]', 'cat', 80, 24)
|
||||
end
|
||||
|
||||
def target_pane
|
||||
@target_pane ||= compute_target_pane
|
||||
end
|
||||
|
||||
def pane_printer
|
||||
PanePrinter.new(fingers_window.pane_tty)
|
||||
end
|
||||
|
||||
def hinter
|
||||
@hinter ||= Fingers::Hinter.new(
|
||||
input: tmux.capture_pane(target_pane.pane_id).chomp,
|
||||
width: target_pane.pane_width.to_i,
|
||||
state: state,
|
||||
output: pane_printer
|
||||
)
|
||||
end
|
||||
|
||||
def view
|
||||
@view ||= ::Fingers::View.new(
|
||||
hinter: hinter,
|
||||
state: state,
|
||||
output: pane_printer,
|
||||
original_pane: target_pane
|
||||
)
|
||||
end
|
||||
|
||||
def state
|
||||
return @state if @state
|
||||
|
||||
@state = State.new
|
||||
|
||||
@state.multi_mode = false
|
||||
@state.show_help = false
|
||||
@state.input = ''
|
||||
@state.modifier = ''
|
||||
@state.selected_hints = []
|
||||
@state.selected_matches = []
|
||||
@state.multi_matches = []
|
||||
@state.exiting = false
|
||||
|
||||
@state
|
||||
end
|
||||
|
||||
def show_hints
|
||||
view.render
|
||||
|
||||
tmux.swap_panes(fingers_pane_id, target_pane.pane_id)
|
||||
tmux.zoom_pane(fingers_pane_id) if pane_was_zoomed?
|
||||
end
|
||||
|
||||
def fingers_pane_id
|
||||
fingers_window.pane_id
|
||||
end
|
||||
|
||||
def handle_input
|
||||
input_socket = InputSocket.new
|
||||
|
||||
tmux.disable_prefix
|
||||
tmux.set_key_table 'fingers'
|
||||
|
||||
Fingers.benchmark_stamp('ready-for-input:end')
|
||||
Fingers.trace_for_tests_do_not_remove_or_the_whole_fabric_of_reality_will_tear_apart_with_unforeseen_consequences('fingers-ready')
|
||||
|
||||
return if Fingers.config.trace_perf == '1'
|
||||
|
||||
input_socket.on_input do |input|
|
||||
view.process_input(input)
|
||||
|
||||
break if state.exiting
|
||||
end
|
||||
end
|
||||
|
||||
def track_options_to_restore!
|
||||
@original_options = {}
|
||||
|
||||
options_to_preserve.each do |option|
|
||||
value = tmux.get_global_option(option)
|
||||
@original_options[option] = value
|
||||
end
|
||||
end
|
||||
|
||||
def restore_options
|
||||
@original_options.each do |option, value|
|
||||
tmux.set_global_option(option, value)
|
||||
end
|
||||
end
|
||||
|
||||
def options_to_preserve
|
||||
%w[prefix]
|
||||
end
|
||||
|
||||
def pane_was_zoomed?
|
||||
target_pane.window_zoomed_flag == '1'
|
||||
end
|
||||
|
||||
def teardown
|
||||
tmux.set_key_table 'root'
|
||||
|
||||
tmux.swap_panes(fingers_pane_id, target_pane.pane_id)
|
||||
tmux.kill_pane(fingers_pane_id)
|
||||
|
||||
tmux.zoom_pane(target_pane.pane_id) if pane_was_zoomed?
|
||||
|
||||
restore_options
|
||||
view.run_action if state.result
|
||||
|
||||
Fingers.trace_for_tests_do_not_remove_or_the_whole_fabric_of_reality_will_tear_apart_with_unforeseen_consequences('fingers-finish')
|
||||
end
|
||||
|
||||
def compute_target_pane
|
||||
from_pane = tmux.pane_by_id(from_pane_id)
|
||||
return from_pane if target == "self"
|
||||
|
||||
sibling_panes = tmux.panes_by_window_id(from_pane.window_id)
|
||||
|
||||
# TODO display message or pick pane
|
||||
return from_pane if sibling_panes.length > 2
|
||||
|
||||
sibling_panes.find { |pane| pane.pane_id != from_pane.pane_id }
|
||||
end
|
||||
end
|
|
@ -1,5 +0,0 @@
|
|||
class Fingers::Commands::TraceStart < Fingers::Commands::Base
|
||||
def run
|
||||
exit 0
|
||||
end
|
||||
end
|
|
@ -1,70 +0,0 @@
|
|||
module Fingers
|
||||
CONFIG_PATH = Fingers::Dirs::CONFIG_PATH
|
||||
|
||||
ConfigStruct = Struct.new(
|
||||
:key,
|
||||
:keyboard_layout,
|
||||
:patterns,
|
||||
:alphabet,
|
||||
:main_action,
|
||||
:ctrl_action,
|
||||
:alt_action,
|
||||
:shift_action,
|
||||
:hint_position,
|
||||
:hint_format,
|
||||
:selected_hint_format,
|
||||
:selected_highlight_format,
|
||||
:highlight_format,
|
||||
:trace_perf
|
||||
) do
|
||||
def initialize(
|
||||
key = 'F',
|
||||
keyboard_layout = 'qwerty',
|
||||
alphabet = [],
|
||||
patterns = [],
|
||||
main_action = ':copy:',
|
||||
ctrl_action = ':open:',
|
||||
alt_action = '',
|
||||
shift_action = ':paste:',
|
||||
hint_position = 'left',
|
||||
hint_format = Tmux.instance.parse_format('fg=yellow,bold'),
|
||||
selected_hint_format = Tmux.instance.parse_format('fg=green,bold'),
|
||||
selected_highlight_format = Tmux.instance.parse_format('fg=green,nobold,dim'),
|
||||
highlight_format = Tmux.instance.parse_format('fg=yellow,nobold,dim'),
|
||||
trace_perf = '0'
|
||||
)
|
||||
super
|
||||
end
|
||||
|
||||
def get_action(modifier)
|
||||
send("#{modifier}_action".to_sym)
|
||||
end
|
||||
end
|
||||
|
||||
def self.config
|
||||
$config ||= Fingers.load_from_cache
|
||||
rescue StandardError
|
||||
$config ||= ConfigStruct.new
|
||||
end
|
||||
|
||||
def self.reset_config
|
||||
$config = ConfigStruct.new
|
||||
end
|
||||
|
||||
def self.save_config
|
||||
File.open(CONFIG_PATH, 'w') do |f|
|
||||
f.write(Marshal.dump(Fingers.config))
|
||||
end
|
||||
end
|
||||
|
||||
def self.load_from_cache
|
||||
Fingers.benchmark_stamp('load-config-from-cache:start')
|
||||
result = Marshal.load(File.open(CONFIG_PATH))
|
||||
Fingers.benchmark_stamp('load-config-from-cache:end')
|
||||
result
|
||||
end
|
||||
|
||||
def self.configure
|
||||
yield config
|
||||
end
|
||||
end
|
|
@ -1,11 +0,0 @@
|
|||
module Fingers::Dirs
|
||||
tmux_pid = (ENV['TMUX'] || ',0000').split(',')[1]
|
||||
FINGERS_REPO_ROOT = Pathname.new(__dir__).parent.parent
|
||||
|
||||
root = Pathname.new(Dir.tmpdir) / 'tmux-fingers'
|
||||
|
||||
LOG_PATH = FINGERS_REPO_ROOT / 'fingers.log'
|
||||
CACHE = root / "tmux-#{tmux_pid}"
|
||||
CONFIG_PATH = CACHE / 'fingers.config'
|
||||
SOCKET_PATH = CACHE / 'fingers.sock'
|
||||
end
|
|
@ -1,128 +0,0 @@
|
|||
class ::Fingers::Hinter
|
||||
def initialize(
|
||||
input:,
|
||||
width:,
|
||||
state:,
|
||||
patterns: Fingers.config.patterns,
|
||||
alphabet: Fingers.config.alphabet,
|
||||
output:,
|
||||
huffman: Huffman.new,
|
||||
formatter: ::Fingers::MatchFormatter.new
|
||||
)
|
||||
@input = input
|
||||
@width = width
|
||||
@hints_by_text = {}
|
||||
@state = state
|
||||
@output = output
|
||||
@formatter = formatter
|
||||
@huffman = huffman
|
||||
@patterns = patterns
|
||||
@alphabet = alphabet
|
||||
end
|
||||
|
||||
def run
|
||||
lines[0..-2].each { |line| process_line(line, "\n") }
|
||||
process_line(lines[-1], '')
|
||||
|
||||
STDOUT.flush
|
||||
output.flush
|
||||
|
||||
build_lookup_table!
|
||||
end
|
||||
|
||||
def lookup(hint)
|
||||
lookup_table[hint]
|
||||
end
|
||||
|
||||
def matches
|
||||
@matches ||= @hints_by_text.keys.uniq.flatten
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :hints,
|
||||
:hints_by_text,
|
||||
:input,
|
||||
:lookup_table,
|
||||
:width,
|
||||
:state,
|
||||
:formatter,
|
||||
:huffman,
|
||||
:output,
|
||||
:patterns,
|
||||
:alphabet
|
||||
|
||||
def build_lookup_table!
|
||||
@lookup_table = hints_by_text.invert
|
||||
end
|
||||
|
||||
def process_line(line, ending)
|
||||
result = line.gsub(pattern) { |_m| replace($~) }
|
||||
output.print(result + ending)
|
||||
end
|
||||
|
||||
def pattern
|
||||
@pattern ||= Regexp.compile("(#{patterns.join('|')})")
|
||||
end
|
||||
|
||||
def hints
|
||||
return @hints if @hints
|
||||
|
||||
@hints = huffman.generate_hints(alphabet: alphabet, n: n_matches)
|
||||
end
|
||||
|
||||
def replace(match)
|
||||
text = match[0]
|
||||
|
||||
captured_text = match && match.named_captures['capture'] || text
|
||||
|
||||
if match.named_captures['capture']
|
||||
match_start, match_end = match.offset(0)
|
||||
capture_start, capture_end = match.offset(:capture)
|
||||
|
||||
capture_offset = [capture_start - match_start, capture_end - capture_start]
|
||||
else
|
||||
capture_offset = nil
|
||||
end
|
||||
|
||||
|
||||
if hints_by_text.has_key?(captured_text)
|
||||
hint = hints_by_text[captured_text]
|
||||
else
|
||||
hint = hints.pop
|
||||
hints_by_text[captured_text] = hint
|
||||
end
|
||||
|
||||
# TODO: this should be output hint without ansi escape sequences
|
||||
formatter.format(
|
||||
hint: hint,
|
||||
highlight: text,
|
||||
selected: state.selected_hints.include?(hint),
|
||||
offset: capture_offset
|
||||
)
|
||||
end
|
||||
|
||||
def lines
|
||||
@lines ||= input.force_encoding('UTF-8').split("\n")
|
||||
end
|
||||
|
||||
def n_matches
|
||||
return @n_matches if @n_matches
|
||||
|
||||
match_set = ::Set.new
|
||||
|
||||
Fingers.benchmark_stamp('counting-matches:start')
|
||||
|
||||
lines.each do |line|
|
||||
line.scan(pattern) do |match|
|
||||
match_set.add($&)
|
||||
end
|
||||
end
|
||||
|
||||
Fingers.benchmark_stamp('counting-matches:end')
|
||||
|
||||
@n_matches = match_set.length
|
||||
|
||||
@n_matches
|
||||
end
|
||||
end
|
|
@ -1,45 +0,0 @@
|
|||
class InputSocket
|
||||
def initialize(path = Fingers::Dirs::SOCKET_PATH)
|
||||
@path = path
|
||||
end
|
||||
|
||||
def on_input
|
||||
remove_socket_file
|
||||
|
||||
loop do
|
||||
socket = server.accept
|
||||
message = socket.readline
|
||||
|
||||
next if message == 'ping'
|
||||
|
||||
yield message
|
||||
end
|
||||
end
|
||||
|
||||
def send_message(cmd)
|
||||
socket = UNIXSocket.new(path)
|
||||
socket.write(cmd)
|
||||
socket.close
|
||||
end
|
||||
|
||||
def wait_for_input
|
||||
send_message 'ping'
|
||||
rescue Errno::ENOENT
|
||||
retry
|
||||
end
|
||||
|
||||
def close
|
||||
server.close
|
||||
remove_socket_file
|
||||
end
|
||||
|
||||
private getter :path
|
||||
|
||||
def server
|
||||
@server ||= UNIXServer.new(path)
|
||||
end
|
||||
|
||||
def remove_socket_file
|
||||
`rm -rf #{path}`
|
||||
end
|
||||
end
|
|
@ -1,21 +0,0 @@
|
|||
require 'logger'
|
||||
|
||||
module Fingers
|
||||
def self.logger
|
||||
return @logger if @logger
|
||||
|
||||
@logger = Logger.new(
|
||||
Fingers::Dirs::LOG_PATH
|
||||
)
|
||||
@logger.level = Logger.const_get(ENV.fetch('FINGERS_LOG_LEVEL', 'INFO'))
|
||||
@logger
|
||||
end
|
||||
|
||||
def self.benchmark_stamp(tag)
|
||||
Fingers.logger.debug("benchmark:#{tag} #{Process.clock_gettime(Process::CLOCK_MONOTONIC)}")
|
||||
end
|
||||
|
||||
def self.trace_for_tests_do_not_remove_or_the_whole_fabric_of_reality_will_tear_apart_with_unforeseen_consequences(msg)
|
||||
Fingers.logger.debug(msg)
|
||||
end
|
||||
end
|
|
@ -1,66 +0,0 @@
|
|||
class ::Fingers::MatchFormatter
|
||||
def initialize(
|
||||
hint_format: Fingers.config.hint_format,
|
||||
highlight_format: Fingers.config.highlight_format,
|
||||
selected_hint_format: Fingers.config.selected_hint_format,
|
||||
selected_highlight_format: Fingers.config.selected_highlight_format,
|
||||
hint_position: Fingers.config.hint_position,
|
||||
reset_sequence: `tput sgr0`.chomp
|
||||
)
|
||||
@hint_format = hint_format
|
||||
@highlight_format = highlight_format
|
||||
@selected_hint_format = selected_hint_format
|
||||
@selected_highlight_format = selected_highlight_format
|
||||
@hint_position = hint_position
|
||||
@reset_sequence = reset_sequence
|
||||
end
|
||||
|
||||
def format(hint:, highlight:, selected:, offset: nil)
|
||||
before_offset(offset, highlight) +
|
||||
format_offset(selected, hint, within_offset(offset, highlight)) +
|
||||
after_offset(offset, highlight)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :hint_format, :highlight_format, :selected_hint_format, :selected_highlight_format, :hint_position, :reset_sequence
|
||||
|
||||
def before_offset(offset, highlight)
|
||||
return "" if offset.nil?
|
||||
start, _ = offset
|
||||
highlight.slice(0, start)
|
||||
end
|
||||
|
||||
def within_offset(offset, highlight)
|
||||
return highlight if offset.nil?
|
||||
start, length = offset
|
||||
highlight.slice(start, length)
|
||||
end
|
||||
|
||||
def after_offset(offset, highlight)
|
||||
return "" if offset.nil?
|
||||
start, length = offset
|
||||
highlight[(start + length)..]
|
||||
end
|
||||
|
||||
def format_offset(selected, hint, highlight)
|
||||
chopped_highlight = chop_highlight(hint, highlight)
|
||||
|
||||
hint_pair = (selected ? selected_hint_format : hint_format) + hint
|
||||
highlight_pair = (selected ? selected_highlight_format : highlight_format) + chopped_highlight
|
||||
|
||||
if hint_position == "right"
|
||||
highlight_pair + hint_pair + reset_sequence
|
||||
else
|
||||
hint_pair + highlight_pair + reset_sequence
|
||||
end
|
||||
end
|
||||
|
||||
def chop_highlight(hint, highlight)
|
||||
if hint_position == 'right'
|
||||
highlight[0..-(hint.length + 1)] || ""
|
||||
else
|
||||
highlight[hint.length..-1] || ""
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,3 +0,0 @@
|
|||
module Fingers
|
||||
VERSION = '2.0.0'.freeze
|
||||
end
|
|
@ -1,104 +0,0 @@
|
|||
class Fingers::View
|
||||
CLEAR_ESCAPE_SEQUENCE = "\e[H\e[J".freeze
|
||||
|
||||
def initialize(hinter:, state:, output:, original_pane:)
|
||||
@hinter = hinter
|
||||
@state = state
|
||||
@output = output
|
||||
@original_pane = original_pane
|
||||
end
|
||||
|
||||
def process_input(input)
|
||||
command, *args = input.gsub(/-/, '_').split(':')
|
||||
send("#{command}_message".to_sym, *args)
|
||||
end
|
||||
|
||||
def render
|
||||
output.print CLEAR_ESCAPE_SEQUENCE
|
||||
hide_cursor
|
||||
hinter.run
|
||||
end
|
||||
|
||||
def run_action
|
||||
Fingers::ActionRunner.new(
|
||||
hint: state.input,
|
||||
modifier: state.modifier,
|
||||
match: state.result,
|
||||
original_pane: original_pane
|
||||
).run
|
||||
end
|
||||
|
||||
def result
|
||||
state.result
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :hinter, :state, :output, :original_pane
|
||||
|
||||
def hide_cursor
|
||||
output.print `tput civis`
|
||||
end
|
||||
|
||||
def toggle_help_message
|
||||
output.print CLEAR_ESCAPE_SEQUENCE
|
||||
output.print 'Help message'
|
||||
end
|
||||
|
||||
def noop_message; end
|
||||
|
||||
def toggle_multi_mode_message
|
||||
prev_state = state.multi_mode
|
||||
state.multi_mode = !state.multi_mode
|
||||
current_state = state.multi_mode
|
||||
|
||||
if prev_state == true && current_state == false
|
||||
state.result = state.multi_matches.join(' ')
|
||||
request_exit!
|
||||
end
|
||||
end
|
||||
|
||||
def exit_message
|
||||
request_exit!
|
||||
end
|
||||
|
||||
# TODO: better naming
|
||||
def hint_message(hint, modifier)
|
||||
state.input += hint
|
||||
state.modifier = modifier
|
||||
|
||||
match = hinter.lookup(state.input)
|
||||
|
||||
handle_match(match) if match
|
||||
end
|
||||
|
||||
def fzf_message
|
||||
file = File.open('/tmp/fingers_fzf', 'w')
|
||||
hinter.matches.each do |match|
|
||||
file.puts match
|
||||
end
|
||||
file.close
|
||||
|
||||
`tmux display-popup -KER "cat /tmp/fingers_fzf | fzf"`
|
||||
end
|
||||
|
||||
def handle_match(match)
|
||||
if state.multi_mode
|
||||
state.multi_matches << match
|
||||
state.selected_hints << state.input
|
||||
state.input = ''
|
||||
render
|
||||
else
|
||||
state.result = match
|
||||
request_exit!
|
||||
end
|
||||
end
|
||||
|
||||
def request_exit!
|
||||
state.exiting = true
|
||||
end
|
||||
|
||||
def tmux
|
||||
@tmux ||= ::Tmux.instance
|
||||
end
|
||||
end
|
118
lib/huffman.rb
118
lib/huffman.rb
|
@ -1,118 +0,0 @@
|
|||
class Huffman
|
||||
class HuffmanNode
|
||||
def initialize(weight:, children:)
|
||||
@weight = weight
|
||||
@children = children
|
||||
end
|
||||
|
||||
def >(other)
|
||||
weight > other.weight
|
||||
end
|
||||
|
||||
def <(other)
|
||||
weight < other.weight
|
||||
end
|
||||
|
||||
def >=(other)
|
||||
weight >= other.weight
|
||||
end
|
||||
|
||||
def <=(other)
|
||||
weight <= other.weight
|
||||
end
|
||||
|
||||
def <=>(other)
|
||||
weight <=> other.weight
|
||||
end
|
||||
|
||||
attr_reader :children, :weight
|
||||
end
|
||||
|
||||
def generate_hints(alphabet:, n:)
|
||||
setup!(alphabet: alphabet, n: n)
|
||||
|
||||
return alphabet if n <= alphabet.length
|
||||
|
||||
first_node = true
|
||||
|
||||
while heap.length > 1
|
||||
if first_node
|
||||
n_branches = initial_number_of_branches
|
||||
first_node = false
|
||||
else
|
||||
n_branches = arity
|
||||
end
|
||||
|
||||
smallest = get_smallest(n_branches)
|
||||
puts "smallest: #{smallest.map { |node| node.weight }}"
|
||||
new_node = new_node_from(smallest)
|
||||
|
||||
heap << new_node
|
||||
end
|
||||
|
||||
result = []
|
||||
|
||||
traverse_tree(heap.elements[1]) do |node, path|
|
||||
puts "node #{node.weight} path: #{path}"
|
||||
result.push(translate_path(path)) if node.children.empty?
|
||||
end
|
||||
|
||||
result.sort_by(&:length)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :alphabet, :n, :heap
|
||||
|
||||
def setup!(alphabet:, n:)
|
||||
@alphabet = alphabet
|
||||
@n = n
|
||||
@heap = build_heap
|
||||
end
|
||||
|
||||
def initial_number_of_branches
|
||||
result = nil
|
||||
|
||||
(1..(n.to_i / arity.to_i + 1)).to_a.each do |t|
|
||||
result = n - t * (arity - 1)
|
||||
|
||||
break if result.between?(2, arity)
|
||||
|
||||
result = arity
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def arity
|
||||
@arity ||= alphabet.length
|
||||
end
|
||||
|
||||
def build_heap
|
||||
queue = PriorityQueue.new
|
||||
|
||||
n.times { |i| queue << HuffmanNode.new(weight: -i, children: []) }
|
||||
|
||||
queue
|
||||
end
|
||||
|
||||
def get_smallest(n)
|
||||
[n, heap.length].min.times.map { heap.pop }
|
||||
end
|
||||
|
||||
def new_node_from(nodes)
|
||||
HuffmanNode.new(weight: nodes.sum(&:weight), children: nodes)
|
||||
end
|
||||
|
||||
def traverse_tree(node, path = [], &block)
|
||||
yield node, path
|
||||
|
||||
node.children.each_with_index do |child, index|
|
||||
traverse_tree(child, [*path, index], &block)
|
||||
end
|
||||
end
|
||||
|
||||
def translate_path(path)
|
||||
path.map { |i| alphabet[i] }.join('')
|
||||
end
|
||||
end
|
|
@ -1,56 +0,0 @@
|
|||
# taken from https://gist.github.com/brianstorti/e20300eb2e7d62b87849
|
||||
class PriorityQueue
|
||||
attr_reader :elements
|
||||
|
||||
def initialize
|
||||
@elements = [nil]
|
||||
end
|
||||
|
||||
def <<(element)
|
||||
@elements << element
|
||||
bubble_up(@elements.size - 1)
|
||||
end
|
||||
|
||||
def pop
|
||||
exchange(1, @elements.size - 1)
|
||||
max = @elements.pop
|
||||
bubble_down(1)
|
||||
max
|
||||
end
|
||||
|
||||
def length
|
||||
elements.length - 1
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def bubble_up(index)
|
||||
parent_index = (index / 2)
|
||||
|
||||
return if index <= 1
|
||||
return if @elements[parent_index] >= @elements[index]
|
||||
|
||||
exchange(index, parent_index)
|
||||
bubble_up(parent_index)
|
||||
end
|
||||
|
||||
def bubble_down(index)
|
||||
child_index = (index * 2)
|
||||
|
||||
return if child_index > @elements.size - 1
|
||||
|
||||
not_the_last_element = child_index < @elements.size - 1
|
||||
left_element = @elements[child_index]
|
||||
right_element = @elements[child_index + 1]
|
||||
child_index += 1 if not_the_last_element && right_element > left_element
|
||||
|
||||
return if @elements[index] >= @elements[child_index]
|
||||
|
||||
exchange(index, child_index)
|
||||
bubble_down(child_index)
|
||||
end
|
||||
|
||||
def exchange(source, target)
|
||||
@elements[source], @elements[target] = @elements[target], @elements[source]
|
||||
end
|
||||
end
|
219
lib/tmux.rb
219
lib/tmux.rb
|
@ -1,219 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# rubocop:disable Metrics/ClassLength
|
||||
class Tmux
|
||||
include Singleton
|
||||
|
||||
Pane = Struct.new(
|
||||
:pane_id,
|
||||
:window_id,
|
||||
:pane_width,
|
||||
:pane_height,
|
||||
:scroll_position,
|
||||
:pane_path,
|
||||
:pane_in_mode,
|
||||
:window_zoomed_flag,
|
||||
:pane_current_path
|
||||
)
|
||||
|
||||
Window = Struct.new(
|
||||
:window_id,
|
||||
:window_height,
|
||||
:window_width,
|
||||
:pane_id,
|
||||
:pane_tty
|
||||
)
|
||||
|
||||
def initialize
|
||||
@format_printer = TmuxFormatPrinter.new
|
||||
end
|
||||
|
||||
def refresh!
|
||||
@panes = nil
|
||||
@windows = nil
|
||||
end
|
||||
|
||||
def panes
|
||||
return @panes if @panes
|
||||
|
||||
format = build_tmux_output_format(Tmux::Pane.members)
|
||||
|
||||
output = `#{tmux} list-panes -a -F '#{format}'`.chomp
|
||||
@panes = parse_tmux_formatted_output(output) do |fields|
|
||||
Pane.new(*fields)
|
||||
end
|
||||
end
|
||||
|
||||
def windows
|
||||
return @windows if @windows
|
||||
|
||||
format = build_tmux_output_format(Tmux::Window.members)
|
||||
|
||||
output = `#{tmux} list-windows -a -F '#{format}'`
|
||||
@windows = parse_tmux_formatted_output(output) do |fields|
|
||||
Window.new(*fields)
|
||||
end
|
||||
end
|
||||
|
||||
def new_session(name, cmd, width, height)
|
||||
flags = []
|
||||
|
||||
flags.push('-f', config_file) if config_file
|
||||
|
||||
`env -u TMUX #{tmux} #{flags.join(' ')} new-session -d -s #{name} -x #{width} -y #{height} '#{cmd}'`
|
||||
end
|
||||
|
||||
def start_server
|
||||
flags = []
|
||||
|
||||
flags.push('-f', config_file) if config_file
|
||||
|
||||
`#{tmux} #{flags.join(' ')} start-server &`
|
||||
end
|
||||
|
||||
def pane_by_id(id)
|
||||
panes.find { |pane| pane['pane_id'] == id }
|
||||
end
|
||||
|
||||
def window_by_id(id)
|
||||
windows.find { |window| window['window_id'] == id }
|
||||
end
|
||||
|
||||
def panes_by_window_id(window_id)
|
||||
panes.select { |pane| pane['window_id'] == window_id }
|
||||
end
|
||||
|
||||
def pane_exec(pane_id, cmd)
|
||||
send_keys(pane_id, " #{cmd}")
|
||||
send_keys(pane_id, 'Enter')
|
||||
end
|
||||
|
||||
def send_keys(pane_id, keys)
|
||||
`#{tmux} send-keys -t '#{pane_id}' '#{keys}'`
|
||||
end
|
||||
|
||||
def capture_pane(pane_id)
|
||||
pane = pane_by_id(pane_id)
|
||||
|
||||
if pane.pane_in_mode == '1'
|
||||
start_line = -pane.scroll_position.to_i
|
||||
end_line = pane.pane_height.to_i - pane.scroll_position.to_i - 1
|
||||
|
||||
`#{tmux} capture-pane -J -p -t '#{pane_id}' -S #{start_line} -E #{end_line}`
|
||||
else
|
||||
`#{tmux} capture-pane -J -p -t '#{pane_id}'`
|
||||
end
|
||||
end
|
||||
|
||||
def create_window(name, cmd, _pane_width, _pane_height)
|
||||
format = build_tmux_output_format(Tmux::Window.members)
|
||||
|
||||
output = `#{tmux} new-window -P -d -n "#{name}" -F '#{format}' "#{cmd}"`.chomp
|
||||
|
||||
parse_tmux_formatted_output(output) do |fields|
|
||||
Window.new(*fields)
|
||||
end.first
|
||||
end
|
||||
|
||||
def swap_panes(src_id, dst_id)
|
||||
# TODO: -Z not supported on all tmux versions
|
||||
system(tmux, 'swap-pane', '-d', '-s', src_id, '-t', dst_id)
|
||||
end
|
||||
|
||||
def kill_pane(id)
|
||||
`#{tmux} kill-pane -t #{id}`
|
||||
end
|
||||
|
||||
def kill_window(id)
|
||||
`#{tmux} kill-window -t #{id}`
|
||||
end
|
||||
|
||||
# TODO: this command is version dependant D:
|
||||
def resize_window(window_id, width, height)
|
||||
system(tmux, 'resize-window', '-t', window_id, '-x', width.to_s, '-y', height.to_s)
|
||||
end
|
||||
|
||||
# TODO: this command is version dependant D:
|
||||
def resize_pane(pane_id, width, height)
|
||||
system(tmux, 'resize-pane', '-t', pane_id, '-x', width.to_s, '-y', height.to_s)
|
||||
end
|
||||
|
||||
def last_pane_id
|
||||
`#{tmux} display -pt':.{last}' '#{pane_id}'`
|
||||
end
|
||||
|
||||
def set_window_option(name, value)
|
||||
system(tmux, 'set-window-option', name, value)
|
||||
end
|
||||
|
||||
def set_key_table(table)
|
||||
system(tmux, 'set-window-option', 'key-table', table)
|
||||
system(tmux, 'switch-client', '-T', table)
|
||||
end
|
||||
|
||||
def disable_prefix
|
||||
set_global_option('prefix', 'None')
|
||||
set_global_option('prefix2', 'None')
|
||||
end
|
||||
|
||||
def set_global_option(name, value)
|
||||
system(tmux, 'set-option', '-g', name, value)
|
||||
end
|
||||
|
||||
def get_global_option(name)
|
||||
`#{tmux} show -gqv #{name}`.chomp
|
||||
end
|
||||
|
||||
def set_buffer(value)
|
||||
return unless value
|
||||
|
||||
system(tmux, 'set-buffer', value)
|
||||
end
|
||||
|
||||
def select_pane(id)
|
||||
system(tmux, 'select-pane', '-t', id)
|
||||
end
|
||||
|
||||
def zoom_pane(id)
|
||||
system(tmux, 'resize-pane', '-Z', '-t', id)
|
||||
end
|
||||
|
||||
def parse_format(format)
|
||||
format_printer.print(format).chomp
|
||||
end
|
||||
|
||||
attr_accessor :socket, :config_file
|
||||
|
||||
private
|
||||
|
||||
attr_reader :format_printer
|
||||
|
||||
def tmux
|
||||
flags = []
|
||||
|
||||
flags.push('-L', socket_flag_value) if socket_flag_value
|
||||
|
||||
return "tmux #{flags.join(' ')}" unless flags.empty?
|
||||
|
||||
'tmux'
|
||||
end
|
||||
|
||||
def build_tmux_output_format(fields)
|
||||
fields.map { |field| format("\#{%<field>s}", field: field) }.join(';')
|
||||
end
|
||||
|
||||
def parse_tmux_formatted_output(output)
|
||||
output.split("\n").map do |line|
|
||||
fields = line.split(';')
|
||||
yield fields
|
||||
end
|
||||
end
|
||||
|
||||
def socket_flag_value
|
||||
return ENV['FINGERS_TMUX_SOCKET'] if ENV['FINGERS_TMUX_SOCKET']
|
||||
socket
|
||||
end
|
||||
end
|
||||
|
||||
#Tmux = TmuxControl
|
||||
# rubocop:enable Metrics/ClassLength
|
|
@ -1,110 +0,0 @@
|
|||
class TmuxFormatPrinter
|
||||
FORMAT_SEPARATOR = /[ ,]+/.freeze
|
||||
|
||||
COLOR_MAP = {
|
||||
black: 0,
|
||||
red: 1,
|
||||
green: 2,
|
||||
yellow: 3,
|
||||
blue: 4,
|
||||
magenta: 5,
|
||||
cyan: 6,
|
||||
white: 7
|
||||
}.freeze
|
||||
|
||||
LAYER_MAP = {
|
||||
bg: 'setab',
|
||||
fg: 'setaf'
|
||||
}.freeze
|
||||
|
||||
STYLE_MAP = {
|
||||
bright: 'bold',
|
||||
bold: 'bold',
|
||||
dim: 'dim',
|
||||
underscore: 'smul',
|
||||
reverse: 'rev',
|
||||
italics: 'sitm'
|
||||
}.freeze
|
||||
|
||||
class ShellExec
|
||||
def exec(cmd)
|
||||
`#{cmd}`.chomp
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(shell: ShellExec.new)
|
||||
@shell = shell
|
||||
end
|
||||
|
||||
def print(input, reset_styles_after: false)
|
||||
@applied_styles = {}
|
||||
|
||||
output = ''
|
||||
|
||||
input.split(FORMAT_SEPARATOR).each do |format|
|
||||
output += parse_format(format)
|
||||
end
|
||||
|
||||
output += reset_sequence if reset_styles_after && !@applied_styles.empty?
|
||||
|
||||
output
|
||||
end
|
||||
|
||||
def parse_format(format)
|
||||
if format.match(/^(bg|fg)=/)
|
||||
parse_color(format)
|
||||
else
|
||||
parse_style(format)
|
||||
end
|
||||
end
|
||||
|
||||
def parse_color(format)
|
||||
match = format.match(/(?<layer>bg|fg)=(?<color>(colou?r(?<color_code>[0-9]+)|.*))/)
|
||||
|
||||
layer = match[:layer].to_sym
|
||||
color = match[:color].to_sym
|
||||
color_code = match[:color_code]
|
||||
|
||||
if match[:color] == 'default'
|
||||
@applied_styles.delete(layer)
|
||||
return reset_to_applied_styles!
|
||||
end
|
||||
|
||||
color_to_apply = color_code || COLOR_MAP[color]
|
||||
|
||||
result = shell.exec("tput #{LAYER_MAP[layer]} #{color_to_apply}")
|
||||
|
||||
@applied_styles[layer] = result
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def parse_style(format)
|
||||
match = format.match(/(?<remove>no)?(?<style>.*)/)
|
||||
should_remove_style = match[:remove] == 'no'
|
||||
style = match[:style].to_sym
|
||||
|
||||
result = shell.exec("tput #{STYLE_MAP[style]}")
|
||||
|
||||
if should_remove_style
|
||||
@applied_styles.delete(style)
|
||||
return reset_to_applied_styles!
|
||||
end
|
||||
|
||||
@applied_styles[style] = result
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def reset_to_applied_styles!
|
||||
[reset_sequence, @applied_styles.values].join
|
||||
end
|
||||
|
||||
def reset_sequence
|
||||
@reset_sequence ||= shell.exec('tput sgr0').chomp.freeze
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :shell
|
||||
end
|
Loading…
Reference in New Issue