remove ruby

This commit is contained in:
Jorge Morante 2023-04-13 08:03:34 +02:00
parent f4188c2cd3
commit 62543f7639
22 changed files with 0 additions and 1566 deletions

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -1,4 +0,0 @@
module Fingers
module Commands
end
end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,7 +0,0 @@
class Fingers::Commands::SendInput < Fingers::Commands::Base
def run
socket = InputSocket.new
socket.send_message(args[1])
end
end

View File

@ -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

View File

@ -1,5 +0,0 @@
class Fingers::Commands::TraceStart < Fingers::Commands::Base
def run
exit 0
end
end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,3 +0,0 @@
module Fingers
VERSION = '2.0.0'.freeze
end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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