wip
This commit is contained in:
parent
4d45983f71
commit
9b50a233be
|
@ -0,0 +1,9 @@
|
|||
root = true
|
||||
|
||||
[*.cr]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
|
@ -25,6 +25,7 @@ GEM
|
|||
rspec-support (3.9.3)
|
||||
|
||||
PLATFORMS
|
||||
arm64-darwin-22
|
||||
ruby
|
||||
x86_64-linux
|
||||
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,3 @@
|
|||
for id in $(tmux list-windows -F "#{window_id}:#{window_name}" | grep 'fingers' | cut -f1 -d:); do
|
||||
tmux kill-window -t $id
|
||||
done
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
version: 1.0
|
||||
shards:
|
||||
priority_queue:
|
||||
git: https://github.com/amadanmath/priority_queue.cr.git
|
||||
version: 0.1.0+git.commit.5ce7c82b876644f28715d384f3615a2dc7b2a66b
|
|
@ -0,0 +1,25 @@
|
|||
require "fingers/commands/start"
|
||||
require "fingers/commands/load_config"
|
||||
require "fingers/commands/send_input"
|
||||
require "fingers/commands/version"
|
||||
|
||||
module Fingers
|
||||
class Cli
|
||||
def run
|
||||
command, *args = ARGV
|
||||
|
||||
cmd = case command
|
||||
when "start"
|
||||
Fingers::Commands::Start.new(args)
|
||||
when "load-config"
|
||||
Fingers::Commands::LoadConfig.new(args)
|
||||
when "send-input"
|
||||
Fingers::Commands::SendInput.new(args)
|
||||
when "version"
|
||||
Fingers::Commands::Version.new(args)
|
||||
end
|
||||
|
||||
cmd.run if cmd
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,12 @@
|
|||
module Fingers::Commands
|
||||
class Base
|
||||
@args : Array(String)
|
||||
|
||||
def initialize(args)
|
||||
@args = args
|
||||
end
|
||||
|
||||
def run
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,212 @@
|
|||
require "fingers/dirs"
|
||||
require "fingers/config"
|
||||
require "tmux"
|
||||
require "file_utils"
|
||||
|
||||
class Fingers::Commands::LoadConfig < Fingers::Commands::Base
|
||||
@fingers_options_names : Array(String) | Nil
|
||||
|
||||
DISALLOWED_CHARS = /cimqn/
|
||||
|
||||
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\\-@]+)+/?)"
|
||||
}
|
||||
|
||||
ALPHABET_MAP = {
|
||||
"qwerty": "asdfqwerzxcvjklmiuopghtybn",
|
||||
"qwerty-homerow": "asdfjklgh",
|
||||
"qwerty-left-hand": "asdfqwerzcxv",
|
||||
"qwerty-right-hand": "jkluiopmyhn",
|
||||
"azerty": "qsdfazerwxcvjklmuiopghtybn",
|
||||
"azerty-homerow": "qsdfjkmgh",
|
||||
"azerty-left-hand": "qsdfazerwxcv",
|
||||
"azerty-right-hand": "jklmuiophyn",
|
||||
"qwertz": "asdfqweryxcvjkluiopmghtzbn",
|
||||
"qwertz-homerow": "asdfghjkl",
|
||||
"qwertz-left-hand": "asdfqweryxcv",
|
||||
"qwertz-right-hand": "jkluiopmhzn",
|
||||
"dvorak": "aoeuqjkxpyhtnsgcrlmwvzfidb",
|
||||
"dvorak-homerow": "aoeuhtnsid",
|
||||
"dvorak-left-hand": "aoeupqjkyix",
|
||||
"dvorak-right-hand": "htnsgcrlmwvz",
|
||||
"colemak": "arstqwfpzxcvneioluymdhgjbk",
|
||||
"colemak-homerow": "arstneiodh",
|
||||
"colemak-left-hand": "arstqwfpzxcv",
|
||||
"colemak-right-hand": "neioluymjhk"
|
||||
}
|
||||
|
||||
def run
|
||||
ensure_cache_folder
|
||||
validate_options!
|
||||
parse_tmux_conf
|
||||
setup_bindings
|
||||
end
|
||||
|
||||
#private
|
||||
|
||||
def parse_tmux_conf
|
||||
options = shell_safe_options
|
||||
|
||||
user_defined_patterns = [] of String
|
||||
|
||||
Fingers.reset_config
|
||||
|
||||
config = Fingers::Config.new
|
||||
|
||||
options.each do |option, value|
|
||||
puts "#{option} => #{value}"
|
||||
|
||||
# TODO generate an enum somehow and use an exhaustive case
|
||||
case option
|
||||
when "key"
|
||||
config.key = value
|
||||
when "keyboard_layout"
|
||||
config.keyboard_layout = value
|
||||
when "main_action"
|
||||
config.main_action = value
|
||||
when "ctrl_action"
|
||||
config.ctrl_action = value
|
||||
when "alt_action"
|
||||
config.alt_action = value
|
||||
when "shift_action"
|
||||
config.shift_action = value
|
||||
when "hint_format"
|
||||
config.hint_format = tmux.parse_format(value)
|
||||
when "selected_hint_format"
|
||||
config.selected_hint_format = tmux.parse_format(value)
|
||||
when "highlight_format"
|
||||
config.highlight_format = tmux.parse_format(value)
|
||||
when "selected_highlight_format"
|
||||
config.selected_highlight_format = tmux.parse_format(value)
|
||||
end
|
||||
|
||||
if option.match(/pattern/)
|
||||
user_defined_patterns.push(value)
|
||||
end
|
||||
end
|
||||
|
||||
config.patterns = clean_up_patterns([
|
||||
*enabled_default_patterns,
|
||||
*user_defined_patterns
|
||||
])
|
||||
|
||||
config.alphabet = ALPHABET_MAP[Fingers.config.keyboard_layout].split("")
|
||||
|
||||
puts config
|
||||
config.save
|
||||
Fingers.reset_config
|
||||
end
|
||||
|
||||
def clean_up_patterns(patterns)
|
||||
patterns.reject { |pattern| pattern.empty? }
|
||||
end
|
||||
|
||||
def setup_bindings
|
||||
`tmux bind-key #{Fingers.config.key} run-shell -b "#{cli} start "\#{pane_id}" self >>#{Fingers::Dirs::LOG_PATH} 2>&1"`
|
||||
`tmux bind-key O run-shell -b "#{cli} start "\#{pane_id}" other >>#{Fingers::Dirs::LOG_PATH} 2>&1"`
|
||||
setup_fingers_mode_bindings
|
||||
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 = {} of String => String
|
||||
|
||||
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)
|
||||
|
||||
# TODO crystal does not support responds_to in runtime i think
|
||||
true
|
||||
#Fingers.config.responds_to?(option_method.to_sym) || option.match(/^@fingers-pattern-\d+$/)
|
||||
end
|
||||
|
||||
def ensure_cache_folder
|
||||
FileUtils.mkdir_p(Fingers::Dirs::CACHE) unless File.exists?(Fingers::Dirs::CACHE)
|
||||
end
|
||||
|
||||
def fingers_options_names
|
||||
@fingers_options_names ||= `tmux show-options -g | grep ^@fingers`.chomp.split("\n").map { |line| line.split(" ")[0] }
|
||||
end
|
||||
|
||||
def unset_tmux_option!(option)
|
||||
`tmux set-option -ug #{option}`
|
||||
end
|
||||
|
||||
def validate_options!
|
||||
errors = [] of String
|
||||
|
||||
fingers_options_names.each do |option|
|
||||
unless valid_option?(option)
|
||||
errors << "#{option} is not a valid option"
|
||||
unset_tmux_option!(option)
|
||||
end
|
||||
end
|
||||
|
||||
return if errors.empty?
|
||||
|
||||
puts "[tmux-fingers] Errors found in tmux.conf:"
|
||||
errors.each { |error| puts " - #{error}" }
|
||||
exit(1)
|
||||
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
|
||||
|
||||
def cli
|
||||
Process.executable_path
|
||||
end
|
||||
|
||||
def tmux
|
||||
Tmux.new
|
||||
end
|
||||
end
|
|
@ -0,0 +1,11 @@
|
|||
require "fingers/commands/base"
|
||||
|
||||
module Fingers::Commands
|
||||
class SendInput < Base
|
||||
def run
|
||||
socket = InputSocket.new
|
||||
|
||||
socket.send_message(@args[0])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,142 @@
|
|||
require "fingers/commands/base"
|
||||
require "fingers/hinter"
|
||||
require "fingers/view"
|
||||
require "fingers/state"
|
||||
require "fingers/input_socket"
|
||||
require "tmux"
|
||||
|
||||
module Fingers::Commands
|
||||
class PanePrinter < Fingers::Printer
|
||||
@pane_tty : String
|
||||
@file : File
|
||||
|
||||
def initialize(pane_tty)
|
||||
@pane_tty = pane_tty
|
||||
@file = File.open(@pane_tty, "w")
|
||||
end
|
||||
|
||||
def print(msg)
|
||||
@file.print(msg)
|
||||
end
|
||||
|
||||
def flush
|
||||
@file.flush
|
||||
#@file.print(@buf)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class Start < Base
|
||||
@original_options : Hash(String, String) = {} of String => String
|
||||
|
||||
def run
|
||||
pane_id, mode = @args
|
||||
|
||||
track_options_to_restore!
|
||||
show_hints
|
||||
|
||||
handle_input
|
||||
|
||||
teardown
|
||||
|
||||
#tmux.swap_panes(target_pane.pane_id, fingers_window.pane_id)
|
||||
#tmux.kill_window(fingers_window.window_id)
|
||||
end
|
||||
|
||||
private def track_options_to_restore!
|
||||
options_to_preserve.each do |option|
|
||||
value = tmux.get_global_option(option)
|
||||
@original_options[option] = value
|
||||
end
|
||||
end
|
||||
|
||||
private def restore_options
|
||||
@original_options.each do |option, value|
|
||||
tmux.set_global_option(option, value)
|
||||
end
|
||||
end
|
||||
|
||||
private def options_to_preserve
|
||||
%w[prefix]
|
||||
end
|
||||
|
||||
private def show_hints
|
||||
view.render
|
||||
|
||||
fingers_pane_id = fingers_window.pane_id
|
||||
|
||||
tmux.swap_panes(fingers_pane_id, target_pane.pane_id)
|
||||
tmux.zoom_pane(fingers_pane_id) if pane_was_zoomed?
|
||||
end
|
||||
|
||||
private def handle_input
|
||||
input_socket = InputSocket.new
|
||||
|
||||
#tmux.disable_prefix
|
||||
tmux.set_key_table "fingers"
|
||||
|
||||
input_socket.on_input do |input|
|
||||
view.process_input(input)
|
||||
break if state.exiting
|
||||
end
|
||||
end
|
||||
|
||||
private def pane_was_zoomed?
|
||||
target_pane.window_zoomed_flag
|
||||
end
|
||||
|
||||
private def teardown
|
||||
tmux.set_key_table "root"
|
||||
|
||||
tmux.swap_panes(fingers_pane_id, target_pane.pane_id)
|
||||
tmux.kill_pane(fingers_pane_id)
|
||||
|
||||
tmux.zoom_pane(target_pane.pane_id) if pane_was_zoomed?
|
||||
|
||||
restore_options
|
||||
#view.run_action if state.result
|
||||
end
|
||||
|
||||
private getter target_pane : Tmux::Pane do
|
||||
tmux.find_pane_by_id(@args[0]).not_nil!
|
||||
end
|
||||
|
||||
private getter fingers_window : Tmux::Window do
|
||||
tmux.create_window("[fingers]", "cat", 80, 24)
|
||||
end
|
||||
|
||||
private getter fingers_pane_id : String do
|
||||
fingers_window.pane_id
|
||||
end
|
||||
|
||||
private getter pane_printer : PanePrinter do
|
||||
PanePrinter.new(fingers_window.pane_tty)
|
||||
end
|
||||
|
||||
private getter state : Fingers::State do
|
||||
::Fingers::State.new
|
||||
end
|
||||
|
||||
private getter hinter : Hinter do
|
||||
Fingers::Hinter.new(
|
||||
input: tmux.capture_pane(target_pane.pane_id),
|
||||
width: target_pane.pane_width.to_i,
|
||||
#state: state,
|
||||
output: pane_printer
|
||||
)
|
||||
end
|
||||
|
||||
private getter view : View do
|
||||
::Fingers::View.new(
|
||||
hinter: hinter,
|
||||
state: state,
|
||||
output: pane_printer,
|
||||
original_pane: target_pane
|
||||
)
|
||||
end
|
||||
|
||||
private getter tmux : Tmux do
|
||||
Tmux.new
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
require "fingers/commands/base"
|
||||
|
||||
module Fingers::Commands
|
||||
class Version < Base
|
||||
def run
|
||||
puts "version"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,56 @@
|
|||
require "json"
|
||||
|
||||
module Fingers
|
||||
struct Config
|
||||
include JSON::Serializable
|
||||
|
||||
property key : String
|
||||
property keyboard_layout : String
|
||||
property patterns : Array(String)
|
||||
property alphabet : Array(String)
|
||||
property main_action : String
|
||||
property ctrl_action : String
|
||||
property alt_action : String
|
||||
property shift_action : String
|
||||
property hint_position : String
|
||||
property hint_format : String
|
||||
property selected_hint_format : String
|
||||
property highlight_format : String
|
||||
property selected_highlight_format : String
|
||||
|
||||
def initialize(
|
||||
@key = "F",
|
||||
@keyboard_layout = "qwerty",
|
||||
@alphabet = [] of String,
|
||||
@patterns = [] of String,
|
||||
@main_action = ":copy:",
|
||||
@ctrl_action = ":open:",
|
||||
@alt_action = "",
|
||||
@shift_action = ":paste:",
|
||||
@hint_position = "left",
|
||||
@hint_format = "fg=yellow,bold",
|
||||
@selected_hint_format = "fg=green,bold",
|
||||
@selected_highlight_format = "fg=green,nobold,dim",
|
||||
@highlight_format = "fg=yellow,nobold,dim",
|
||||
)
|
||||
end
|
||||
|
||||
def self.load_from_cache
|
||||
Config.from_json(File.open(::Fingers::Dirs::CONFIG_PATH))
|
||||
end
|
||||
|
||||
def save
|
||||
to_json(File.open(::Fingers::Dirs::CONFIG_PATH, "w"))
|
||||
end
|
||||
end
|
||||
|
||||
def self.config
|
||||
@@config ||= Config.load_from_cache
|
||||
rescue
|
||||
@@config ||= Config.new
|
||||
end
|
||||
|
||||
def self.reset_config
|
||||
@@config = nil
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
# TODO maybe use some xgd shite here?
|
||||
|
||||
module Fingers::Dirs
|
||||
TMUX_PID = (ENV["TMUX"] || ",0000").split(",")[1]
|
||||
FINGERS_REPO_ROOT = Pathname.new(__dir__).parent.parent
|
||||
|
||||
ROOT = Path["~/.tmux"].expand(home: true)
|
||||
|
||||
LOG_PATH = ROOT / "fingers.log"
|
||||
CACHE = ROOT / "cr-tmux-#{TMUX_PID}"
|
||||
CONFIG_PATH = CACHE / "fingers.config"
|
||||
SOCKET_PATH = CACHE / "fingers.sock"
|
||||
end
|
|
@ -0,0 +1,150 @@
|
|||
require "huffman"
|
||||
require "fingers/config"
|
||||
require "fingers/match_formatter"
|
||||
require "fingers/types"
|
||||
|
||||
module Fingers
|
||||
class Hinter
|
||||
@formatter : Formatter
|
||||
@patterns : Array(String)
|
||||
@alphabet : Array(String)
|
||||
@lines : Array(String) | Nil
|
||||
@pattern : Regex | Nil
|
||||
@hints : Array(String) | Nil
|
||||
@n_matches: Int32 | Nil
|
||||
|
||||
def initialize(
|
||||
input : String,
|
||||
width : Int32,
|
||||
#state,
|
||||
output : Printer,
|
||||
patterns = Fingers.config.patterns,
|
||||
alphabet = Fingers.config.alphabet,
|
||||
huffman = Huffman.new,
|
||||
formatter = ::Fingers::MatchFormatter.new
|
||||
)
|
||||
@input = input
|
||||
@width = width
|
||||
@hints_by_text = {} of String => String
|
||||
@lookup_table = {} of String => String
|
||||
#@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.fetch(hint) { nil }
|
||||
end
|
||||
|
||||
def matches
|
||||
@matches ||= @hints_by_text.keys.uniq.flatten
|
||||
end
|
||||
|
||||
#private
|
||||
|
||||
private getter :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 : Regex
|
||||
@pattern ||= Regex.new("(#{patterns.join('|')})")
|
||||
end
|
||||
|
||||
def hints : Array(String)
|
||||
return @hints.as(Array(String)) if !@hints.nil?
|
||||
|
||||
@hints = huffman.generate_hints(alphabet: alphabet, n: n_matches)
|
||||
end
|
||||
|
||||
def replace(match)
|
||||
text = match[0]
|
||||
|
||||
#captured_text = match && match.named_captures["capture"] || text
|
||||
captured_text = 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
|
||||
|
||||
raise "Too many matches" if hint.nil?
|
||||
|
||||
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),
|
||||
selected: false,
|
||||
offset: nil
|
||||
)
|
||||
end
|
||||
|
||||
def lines : Array(String)
|
||||
@lines ||= input.split("\n")
|
||||
end
|
||||
|
||||
def n_matches : Int32
|
||||
return @n_matches.as(Int32) if !@n_matches.nil?
|
||||
|
||||
match_set = Set(String).new
|
||||
|
||||
#Fingers.benchmark_stamp('counting-matches:start')
|
||||
|
||||
lines.each do |line|
|
||||
line.scan(pattern) do |match|
|
||||
# TODO hey cuidao
|
||||
match_set.add(match.to_a.first || "")
|
||||
end
|
||||
end
|
||||
|
||||
#Fingers.benchmark_stamp('counting-matches:end')
|
||||
|
||||
@n_matches = match_set.size
|
||||
|
||||
match_set.size
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,44 @@
|
|||
require "socket"
|
||||
require "fingers/dirs"
|
||||
|
||||
module Fingers
|
||||
class InputSocket
|
||||
@path : String
|
||||
|
||||
def initialize(path = Fingers::Dirs::SOCKET_PATH.to_s)
|
||||
@path = path
|
||||
end
|
||||
|
||||
def on_input
|
||||
remove_socket_file
|
||||
|
||||
loop do
|
||||
socket = server.accept
|
||||
message = socket.gets
|
||||
|
||||
yield (message || "")
|
||||
end
|
||||
end
|
||||
|
||||
def send_message(cmd)
|
||||
socket = UNIXSocket.new(path)
|
||||
socket.puts(cmd)
|
||||
socket.close
|
||||
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
|
||||
end
|
|
@ -33,9 +33,7 @@ class InputSocket
|
|||
remove_socket_file
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :path
|
||||
private getter :path
|
||||
|
||||
def server
|
||||
@server ||= UNIXServer.new(path)
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
require "fingers/config"
|
||||
require "fingers/types"
|
||||
|
||||
module Fingers
|
||||
class MatchFormatter < Fingers::Formatter
|
||||
def initialize(
|
||||
hint_format : String = Fingers.config.hint_format,
|
||||
highlight_format : String = Fingers.config.highlight_format,
|
||||
selected_hint_format : String = Fingers.config.selected_hint_format,
|
||||
selected_highlight_format : String = Fingers.config.selected_highlight_format,
|
||||
hint_position : String = Fingers.config.hint_position,
|
||||
reset_sequence : String = `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 : String, highlight : String, selected : Bool, offset : Tuple(Int32, Int32) | Nil)
|
||||
before_offset(offset, highlight) +
|
||||
format_offset(selected, hint, within_offset(offset, highlight)) +
|
||||
after_offset(offset, highlight)
|
||||
end
|
||||
|
||||
private getter :hint_format, :highlight_format, :selected_hint_format, :selected_highlight_format, :hint_position, :reset_sequence
|
||||
|
||||
private def before_offset(offset, highlight)
|
||||
return "" if offset.nil?
|
||||
start, _ = offset
|
||||
highlight[0..(start - 1)]
|
||||
end
|
||||
|
||||
private def within_offset(offset, highlight)
|
||||
return highlight if offset.nil?
|
||||
start, length = offset
|
||||
highlight[start..(start + length - 1)]
|
||||
end
|
||||
|
||||
private def after_offset(offset, highlight)
|
||||
return "" if offset.nil?
|
||||
start, length = offset
|
||||
highlight[(start + length)..]
|
||||
end
|
||||
|
||||
private 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
|
||||
|
||||
private def chop_highlight(hint, highlight)
|
||||
if hint_position == "right"
|
||||
highlight[0..-(hint.size + 1)] || ""
|
||||
else
|
||||
highlight[hint.size..-1] || ""
|
||||
end
|
||||
rescue
|
||||
puts "failed for hint '#{hint}' and '#{highlight}'"
|
||||
""
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,25 @@
|
|||
module Fingers
|
||||
class State
|
||||
def initialize
|
||||
@show_help = false
|
||||
@multi_mode = false
|
||||
@input = ""
|
||||
@modifier = ""
|
||||
@selected_hints = [] of String
|
||||
@selected_matches = [] of String
|
||||
@multi_matches = [] of String
|
||||
@result = ""
|
||||
@exiting = false
|
||||
end
|
||||
|
||||
property :show_help,
|
||||
:multi_mode,
|
||||
:input,
|
||||
:modifier,
|
||||
:selected_hints,
|
||||
:selected_matches,
|
||||
:multi_matches,
|
||||
:result,
|
||||
:exiting
|
||||
end
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
module Fingers
|
||||
abstract class Printer
|
||||
abstract def print(msg : String)
|
||||
abstract def flush()
|
||||
end
|
||||
|
||||
abstract class Formatter
|
||||
abstract def format(hint : String, highlight : String, selected : Bool, offset : Tuple(Int32, Int32) | Nil)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,74 @@
|
|||
require "tmux"
|
||||
require "fingers/hinter"
|
||||
|
||||
module Fingers
|
||||
class View
|
||||
CLEAR_ESCAPE_SEQUENCE = "\e[H\e[J"
|
||||
|
||||
@hinter : Hinter
|
||||
@state : State
|
||||
@output : Printer
|
||||
@original_pane : Tmux::Pane
|
||||
|
||||
def initialize(
|
||||
@hinter,
|
||||
@output,
|
||||
@original_pane,
|
||||
@state
|
||||
)
|
||||
end
|
||||
|
||||
def render
|
||||
output.print CLEAR_ESCAPE_SEQUENCE
|
||||
hinter.run
|
||||
end
|
||||
|
||||
def process_input(input : String)
|
||||
command, *args = input.split(":")
|
||||
|
||||
case command
|
||||
when "hint"
|
||||
char, modifier = args
|
||||
hint(char, modifier)
|
||||
when "exit"
|
||||
request_exit!
|
||||
when "toggle-help"
|
||||
when "toggle-toggle-multi-mode"
|
||||
when "fzf"
|
||||
# soon
|
||||
end
|
||||
end
|
||||
|
||||
private def hide_cursor
|
||||
output.print `tput civis`
|
||||
end
|
||||
|
||||
private def hint(char, modifier)
|
||||
state.input += char
|
||||
state.modifier = modifier
|
||||
match = hinter.lookup(state.input)
|
||||
|
||||
match = hinter.lookup(state.input)
|
||||
|
||||
handle_match(match) if match
|
||||
end
|
||||
|
||||
private getter :output, :hinter, :original_pane, :state
|
||||
|
||||
private 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
|
||||
|
||||
private def request_exit!
|
||||
state.exiting = true
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,135 @@
|
|||
require "priority_queue"
|
||||
|
||||
class HuffmanNode
|
||||
def initialize(weight : Int32, children : Array(HuffmanNode))
|
||||
@weight = weight
|
||||
@children = children
|
||||
end
|
||||
|
||||
getter :children, :weight
|
||||
end
|
||||
|
||||
class Huffman
|
||||
@alphabet : Array(String)
|
||||
@n : Int32
|
||||
|
||||
getter :alphabet, :n, :queue
|
||||
|
||||
def initialize
|
||||
@n = 0
|
||||
@queue = PriorityQueue(HuffmanNode).new
|
||||
@alphabet = [] of String
|
||||
end
|
||||
|
||||
def generate_hints(alphabet : Array(String), n : Int32)
|
||||
puts "generating for n: #{n} alphabet: #{alphabet}"
|
||||
|
||||
setup!(alphabet: alphabet, n: n)
|
||||
|
||||
return alphabet if n <= alphabet.size
|
||||
|
||||
first_node = true
|
||||
|
||||
while queue.size > 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)
|
||||
|
||||
queue.push(new_node.weight, new_node)
|
||||
end
|
||||
|
||||
result = [] of String
|
||||
|
||||
root = queue.pop
|
||||
|
||||
puts root.weight
|
||||
|
||||
#traverse_inline(root)
|
||||
|
||||
traverse_tree(root) do |node, path|
|
||||
#puts "node #{node.weight} path: #{path}"
|
||||
result.push(translate_path(path)) if node.children.empty?
|
||||
end
|
||||
|
||||
result.sort_by { |hint| hint.size }
|
||||
end
|
||||
|
||||
#private
|
||||
|
||||
#attr_reader :alphabet, :n, :heap
|
||||
|
||||
def setup!(alphabet, n)
|
||||
@alphabet = alphabet
|
||||
@n = n
|
||||
@queue = build_heap
|
||||
end
|
||||
|
||||
def initial_number_of_branches
|
||||
result = 1
|
||||
|
||||
(1..(n.to_i // arity.to_i + 1)).to_a.each do |t|
|
||||
result = n - t * (arity - 1)
|
||||
|
||||
break if result >= 2 && result <= arity
|
||||
|
||||
result = arity
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def arity
|
||||
alphabet.size
|
||||
end
|
||||
|
||||
def build_heap
|
||||
queue = PriorityQueue(HuffmanNode).new
|
||||
|
||||
n.times { |i| queue.push(-i.to_i, HuffmanNode.new(weight: -i, children: [] of HuffmanNode)) }
|
||||
|
||||
queue
|
||||
end
|
||||
|
||||
def get_smallest(n : Int32) : Array(HuffmanNode)
|
||||
puts "n: #{n}"
|
||||
puts "queue.size: #{queue.size}"
|
||||
result = [] of HuffmanNode
|
||||
[n, queue.size].min.times.each { result.push(queue.pop) }
|
||||
result
|
||||
end
|
||||
|
||||
def new_node_from(nodes)
|
||||
weight = nodes.sum do |node|
|
||||
node.weight
|
||||
end
|
||||
|
||||
HuffmanNode.new(weight: weight, children: nodes)
|
||||
end
|
||||
|
||||
def traverse_tree(node, path = [] of Int32, &block : (HuffmanNode, Array(Int32)) -> Nil)
|
||||
yield node, path
|
||||
|
||||
node.children.each_with_index do |child, index|
|
||||
traverse_tree(child, [*path, index], &block)
|
||||
end
|
||||
end
|
||||
|
||||
def traverse_inline(node, path = [] of Int32)
|
||||
puts "[inline] node: #{node} #{node.weight}, path: #{path}"
|
||||
|
||||
node.children.each_with_index do |child, index|
|
||||
traverse_inline(child, [*path, index])
|
||||
end
|
||||
end
|
||||
|
||||
def translate_path(path)
|
||||
path.map { |i| alphabet[i] }.join("")
|
||||
end
|
||||
end
|
|
@ -44,6 +44,7 @@ class Huffman
|
|||
end
|
||||
|
||||
smallest = get_smallest(n_branches)
|
||||
puts "smallest: #{smallest.map { |node| node.weight }}"
|
||||
new_node = new_node_from(smallest)
|
||||
|
||||
heap << new_node
|
||||
|
@ -52,6 +53,7 @@ class Huffman
|
|||
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
|
||||
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
class PriorityQueue(T)
|
||||
@q : Hash(Int32, Array(T))
|
||||
|
||||
def initialize(data=nil)
|
||||
@q = Hash(Int32, Array(T)).new do |h, k|
|
||||
h[k] = [] of T
|
||||
end
|
||||
data.each {|priority, item| @q[priority] << item} if data
|
||||
@priorities = @q.keys.sort
|
||||
end
|
||||
|
||||
def push(priority : Int32, item : T)
|
||||
@q[priority].push(item)
|
||||
@priorities = @q.keys.sort
|
||||
end
|
||||
|
||||
def pop
|
||||
p = @priorities.last
|
||||
item = @q[p].shift
|
||||
if @q[p].empty?
|
||||
@q.delete(p)
|
||||
@priorities.pop
|
||||
end
|
||||
item
|
||||
end
|
||||
|
||||
def peek
|
||||
unless empty?
|
||||
@q[@priorities[0]][0]
|
||||
end
|
||||
end
|
||||
|
||||
def empty?
|
||||
@priorities.empty?
|
||||
end
|
||||
|
||||
def each
|
||||
@q.each do |priority, items|
|
||||
items.each {|item| yield priority, item}
|
||||
end
|
||||
end
|
||||
|
||||
def dup
|
||||
@q.each_with_object(self.class.new) do |(priority, items), obj|
|
||||
items.each {|item| obj.push(priority, item)}
|
||||
end
|
||||
end
|
||||
|
||||
def merge(other)
|
||||
raise TypeError unless self.class == other.class
|
||||
pq = dup
|
||||
other.each {|priority, item| pq.push(priority, item)}
|
||||
pq # return a new object
|
||||
end
|
||||
|
||||
def inspect
|
||||
@q.inspect
|
||||
end
|
||||
|
||||
def size
|
||||
@q.values.sum { |list| list.size }
|
||||
end
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
[*.cr]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
|
@ -0,0 +1,8 @@
|
|||
/doc/
|
||||
/lib/
|
||||
/bin/
|
||||
/.shards/
|
||||
|
||||
# Libraries don't need dependency lock
|
||||
# Dependencies will be locked in application that uses them
|
||||
/shard.lock
|
|
@ -0,0 +1 @@
|
|||
language: crystal
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Goran Topic
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,41 @@
|
|||
# priority_queue
|
||||
|
||||
A simple priority queue implementation, inspired by the article
|
||||
[Implementing a Priority Queue in Ruby](http://www.brianstorti.com/implementing-a-priority-queue-in-ruby/) by Brian Storti, written mostly
|
||||
as an exercise in Crystal.
|
||||
|
||||
## Installation
|
||||
|
||||
Add this to your application's `shard.yml`:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
priority_queue:
|
||||
github: amadanmath/priority_queue.cr
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```crystal
|
||||
require "priority_queue"
|
||||
|
||||
queue = PriorityQueue(Int32, String).new
|
||||
queue[3] = "Three"
|
||||
queue[100] = "Hundred"
|
||||
queue[0] = "Zero"
|
||||
queue.pop # => "Hundred"
|
||||
queue.pop # => "Three"
|
||||
queue.size # => 1
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork it ( https://github.com/amadanmath/priority_queue.cr/fork )
|
||||
2. Create your feature branch (git checkout -b my-new-feature)
|
||||
3. Commit your changes (git commit -am 'Add some feature')
|
||||
4. Push to the branch (git push origin my-new-feature)
|
||||
5. Create a new Pull Request
|
||||
|
||||
## Contributors
|
||||
|
||||
- [amadanmath](https://github.com/amadanmath) Goran Topic - creator, maintainer
|
|
@ -0,0 +1 @@
|
|||
..
|
|
@ -0,0 +1,9 @@
|
|||
name: priority_queue
|
||||
version: 0.1.0
|
||||
|
||||
authors:
|
||||
- Goran Topic <amadanmath+github@gmail.com>
|
||||
|
||||
crystal: 0.24.1
|
||||
|
||||
license: MIT
|
|
@ -0,0 +1,96 @@
|
|||
require "./spec_helper"
|
||||
|
||||
describe PriorityQueue do
|
||||
it "can be constructed with hash-like type literal" do
|
||||
queue = PriorityQueue{
|
||||
2 => "Two",
|
||||
6 => "Six",
|
||||
3 => "Three"
|
||||
}
|
||||
queue.size.should eq 3
|
||||
queue.pop.should eq "Six"
|
||||
end
|
||||
|
||||
it "removes highest-priority element" do
|
||||
queue = PriorityQueue(Int32, String).new
|
||||
queue[2] = "Two"
|
||||
queue[6] = "Six"
|
||||
queue[3] = "Three"
|
||||
queue[1] = "One"
|
||||
queue[4] = "Four"
|
||||
queue[5] = "Five"
|
||||
queue.pop.should eq "Six"
|
||||
queue.pop.should eq "Five"
|
||||
queue.pop.should eq "Four"
|
||||
queue.pop.should eq "Three"
|
||||
queue.pop.should eq "Two"
|
||||
queue.pop.should eq "One"
|
||||
end
|
||||
|
||||
it "yields to block if popped when empty" do
|
||||
queue = PriorityQueue(Int32, String).new
|
||||
yielded = false
|
||||
queue.pop { yielded = true }
|
||||
yielded.should be_true
|
||||
end
|
||||
|
||||
it "raises an error if popped without block when empty" do
|
||||
queue = PriorityQueue(Int32, String).new
|
||||
expect_raises(IndexError) { queue.pop }
|
||||
end
|
||||
|
||||
it "can peek at the top element and its priority" do
|
||||
queue = PriorityQueue{
|
||||
2 => "Two",
|
||||
6 => "Six",
|
||||
3 => "Three"
|
||||
}
|
||||
queue.peek?.should eq "Six"
|
||||
queue.priority?.should eq 6
|
||||
queue.pop
|
||||
queue.peek?.should eq "Three"
|
||||
queue.priority?.should eq 3
|
||||
queue.pop
|
||||
queue.peek?.should eq "Two"
|
||||
queue.priority?.should eq 2
|
||||
queue.pop
|
||||
queue.peek?.should be_nil
|
||||
queue.priority?.should be_nil
|
||||
end
|
||||
|
||||
it "stringifies correctly" do
|
||||
queue = PriorityQueue{
|
||||
2 => "Two",
|
||||
6 => "Six",
|
||||
3 => "Three"
|
||||
}
|
||||
queue.inspect.should eq "PriorityQueue{6 => \"Six\"... [+2]}"
|
||||
queue.pop
|
||||
queue.inspect.should eq "PriorityQueue{3 => \"Three\"... [+1]}"
|
||||
queue.pop
|
||||
queue.inspect.should eq "PriorityQueue{2 => \"Two\"}"
|
||||
queue.pop
|
||||
queue.inspect.should eq "PriorityQueue{}"
|
||||
end
|
||||
|
||||
it "can duplicate" do
|
||||
array = [1]
|
||||
queue1 = PriorityQueue{
|
||||
1 => array
|
||||
}
|
||||
queue2 = queue1.dup
|
||||
queue1.pop.pop
|
||||
queue1.size.should eq 0
|
||||
queue2.pop.size.should eq 0
|
||||
queue2.size.should eq 0
|
||||
end
|
||||
|
||||
it "can return all elements in order" do
|
||||
queue = PriorityQueue{
|
||||
2 => "Two",
|
||||
6 => "Six",
|
||||
3 => "Three"
|
||||
}
|
||||
queue.to_a.should eq ["Six", "Three", "Two"]
|
||||
end
|
||||
end
|
|
@ -0,0 +1,2 @@
|
|||
require "spec"
|
||||
require "../src/priority_queue"
|
|
@ -0,0 +1,173 @@
|
|||
require "./priority_queue/*"
|
||||
|
||||
# Implements a priority queue data structure. Each element is tagged
|
||||
# with a priority, which is a Comparable; `pop` removes and returns
|
||||
# the element with the highest priority.
|
||||
class PriorityQueue(P, T)
|
||||
# The class used in the underlying array
|
||||
private class Element(P, T)
|
||||
include Comparable(Element)
|
||||
|
||||
getter priority : P
|
||||
getter item : T
|
||||
|
||||
def initialize(@priority : P, @item : T)
|
||||
end
|
||||
|
||||
def <=>(other : Element)
|
||||
priority <=> other.priority
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Returns the underlying array
|
||||
protected property elements = [] of Element(P, T)
|
||||
|
||||
# Returns `true` if the queue is empty, `false` otherwise
|
||||
def empty?
|
||||
return @elements.empty?
|
||||
end
|
||||
|
||||
# Removes and returns the item with the highest priority.
|
||||
# If the queue is empty, it yields the block.
|
||||
def pop
|
||||
if empty?
|
||||
yield
|
||||
else
|
||||
exchange(0, @elements.size - 1)
|
||||
max = @elements.pop
|
||||
bubble_down(0)
|
||||
max.item
|
||||
end
|
||||
end
|
||||
|
||||
# Like `#pop`, but raises `IndexError` if the queue is empty.
|
||||
def pop
|
||||
pop { raise IndexError.new }
|
||||
end
|
||||
|
||||
# Like `#pop`, but returns `nil` if the queue is empty.
|
||||
def pop?
|
||||
pop { nil }
|
||||
end
|
||||
|
||||
# Returns the item with the highest priority (without removing it).
|
||||
# If the queue is empty, it yields the block.
|
||||
def peek
|
||||
if empty?
|
||||
yield
|
||||
else
|
||||
@elements[0].item
|
||||
end
|
||||
end
|
||||
|
||||
# Like `#peek`, but raises `IndexError` if the queue is empty.
|
||||
def peek
|
||||
peek { raise IndexError.new }
|
||||
end
|
||||
|
||||
# Like `#peek`, but returns `nil` if the queue is empty.
|
||||
def peek?
|
||||
peek { nil }
|
||||
end
|
||||
|
||||
# Returns the highest priority item's priority (without removing it).
|
||||
# If the queue is empty, it yields the block.
|
||||
def priority
|
||||
if empty?
|
||||
yield
|
||||
else
|
||||
@elements[0].priority
|
||||
end
|
||||
end
|
||||
|
||||
# Like `#priority`, but raises `IndexError` if the queue is empty.
|
||||
def priority
|
||||
priority { raise IndexError.new }
|
||||
end
|
||||
|
||||
# Like `#priority`, but returns `nil` if the queue is empty.
|
||||
def priority?
|
||||
priority { nil }
|
||||
end
|
||||
|
||||
# Returns the current number of elements in the queue
|
||||
def size
|
||||
@elements.size
|
||||
end
|
||||
|
||||
# Returns a human-readable string representation
|
||||
def inspect(io : IO)
|
||||
io << "PriorityQueue{"
|
||||
unless empty?
|
||||
priority.inspect(io)
|
||||
io << " => "
|
||||
peek.inspect(io)
|
||||
if size > 1
|
||||
io << "... [+"
|
||||
(size - 1).inspect(io)
|
||||
io << "]"
|
||||
end
|
||||
end
|
||||
io << "}"
|
||||
end
|
||||
|
||||
# Inserts a new element into the queue, at given priority. `priority` needs to be a `Comparable`.
|
||||
def []=(priority, object)
|
||||
@elements << Element.new(priority, object)
|
||||
bubble_up(@elements.size - 1)
|
||||
end
|
||||
|
||||
# Returns a new `PriorityQueue` that has exactly `self`'s elements.
|
||||
def dup
|
||||
other = PriorityQueue(P, T).new
|
||||
other.elements = elements.dup
|
||||
other
|
||||
end
|
||||
|
||||
# Yields each element in the queue in order of priority
|
||||
def each
|
||||
copy = dup
|
||||
until copy.empty?
|
||||
yield copy.pop
|
||||
end
|
||||
end
|
||||
|
||||
# Returns an array containing each element in the queue in order of priority
|
||||
def to_a
|
||||
array = [] of T
|
||||
each do |element|
|
||||
array << element
|
||||
end
|
||||
array
|
||||
end
|
||||
|
||||
|
||||
private def bubble_up(index)
|
||||
return if index == 0
|
||||
|
||||
parent_index = (index - 1) / 2
|
||||
return if @elements[parent_index] >= @elements[index]
|
||||
|
||||
exchange(index, parent_index)
|
||||
bubble_up(parent_index)
|
||||
end
|
||||
|
||||
private def bubble_down(index)
|
||||
child_index = index * 2 + 1
|
||||
return if child_index >= @elements.size
|
||||
|
||||
not_the_last_element = child_index < @elements.size - 1
|
||||
left_element = @elements[child_index]
|
||||
|
||||
child_index += 1 if not_the_last_element && @elements[child_index + 1] > left_element
|
||||
return if @elements[index] >= @elements[child_index]
|
||||
|
||||
exchange(index, child_index)
|
||||
bubble_down(child_index)
|
||||
end
|
||||
|
||||
private def exchange(source, target)
|
||||
@elements[source], @elements[target] = @elements[target], @elements[source]
|
||||
end
|
||||
end
|
|
@ -0,0 +1,3 @@
|
|||
class PriorityQueue(P, T)
|
||||
VERSION = "0.2.0"
|
||||
end
|
|
@ -0,0 +1,261 @@
|
|||
require "json"
|
||||
require "tmux_format_printer"
|
||||
|
||||
def to_tmux_string(value)
|
||||
# TODO tmux syntax to escape quotes
|
||||
"\"\#{#{value}}\""
|
||||
end
|
||||
|
||||
def to_tmux_number(value)
|
||||
"\#{#{value}}"
|
||||
end
|
||||
|
||||
def to_tmux_bool(value)
|
||||
"\#{?#{value},true,false}"
|
||||
end
|
||||
|
||||
def build_tmux_format(hash)
|
||||
fields = hash.map do |field, type|
|
||||
if type == String
|
||||
"\"#{field}\": #{to_tmux_string(field)}"
|
||||
elsif type == Int32
|
||||
"\"#{field}\": #{to_tmux_number(field)}"
|
||||
elsif type == Bool
|
||||
"\"#{field}\": #{to_tmux_bool(field)}"
|
||||
end
|
||||
end
|
||||
|
||||
"{#{fields.join(",")}}"
|
||||
end
|
||||
|
||||
# TODO maybe use system everywhere?
|
||||
|
||||
# rubocop:disable Metrics/ClassLength
|
||||
class Tmux
|
||||
struct Pane
|
||||
include JSON::Serializable
|
||||
|
||||
property pane_id : String
|
||||
property window_id : String
|
||||
property pane_width : Int32
|
||||
property pane_height : Int32
|
||||
property pane_current_path : String
|
||||
property pane_in_mode : Bool
|
||||
#property scroll_position : Int32
|
||||
property window_zoomed_flag : Bool
|
||||
end
|
||||
|
||||
struct Window
|
||||
include JSON::Serializable
|
||||
|
||||
property window_id : String
|
||||
property window_width : Int32
|
||||
property window_height : Int32
|
||||
property pane_id : String
|
||||
property pane_tty : String
|
||||
end
|
||||
|
||||
# TODO make a macro or something
|
||||
PANE_FORMAT = build_tmux_format({
|
||||
pane_id: String,
|
||||
window_id: String,
|
||||
pane_width: Int32,
|
||||
pane_height: Int32,
|
||||
pane_current_path: String,
|
||||
pane_in_mode: Bool,
|
||||
#scroll_position: Int32,
|
||||
window_zoomed_flag: Bool,
|
||||
})
|
||||
|
||||
WINDOW_FORMAT = build_tmux_format({
|
||||
window_id: String,
|
||||
window_width: Int32,
|
||||
window_height: Int32,
|
||||
pane_id: String,
|
||||
pane_tty: String,
|
||||
})
|
||||
|
||||
@panes : Array(Pane) | Nil
|
||||
|
||||
#def refresh!
|
||||
#@panes = nil
|
||||
#@windows = nil
|
||||
#end
|
||||
|
||||
def panes : Array(Pane)
|
||||
`#{tmux} list-panes -a -F '#{PANE_FORMAT}'`.chomp.split("\n").map do |pane|
|
||||
Pane.from_json(pane)
|
||||
end
|
||||
end
|
||||
|
||||
def find_pane_by_id(id) : Pane | Nil
|
||||
panes.find { |pane| pane.pane_id == id }
|
||||
end
|
||||
|
||||
def windows
|
||||
`#{tmux} list-windows -a -F '#{WINDOW_FORMAT}'`.chomp.split("\n").map do |pane|
|
||||
Window.from_json(pane)
|
||||
end
|
||||
end
|
||||
|
||||
def new_session(name, cmd, width, height)
|
||||
flags: Array(String) = [] of String
|
||||
|
||||
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 = [] of String
|
||||
|
||||
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)
|
||||
|
||||
return "" unless pane
|
||||
|
||||
#if pane.pane_in_mode
|
||||
#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}'`.chomp
|
||||
#end
|
||||
end
|
||||
|
||||
def create_window(name, cmd, _pane_width, _pane_height)
|
||||
output = `#{tmux} new-window -P -d -n '#{name}' -F '#{WINDOW_FORMAT}' '#{cmd}'`.chomp
|
||||
|
||||
Window.from_json(output)
|
||||
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
|
||||
|
||||
# TODO
|
||||
def parse_format(format)
|
||||
format_printer.print(format).chomp
|
||||
end
|
||||
|
||||
def format_printer
|
||||
@format_printer ||= TmuxFormatPrinter.new
|
||||
end
|
||||
|
||||
def tmux
|
||||
flags = [] of String
|
||||
|
||||
#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
|
||||
|
||||
def display_message(msg)
|
||||
`#{tmux} display-message "#{msg}"`
|
||||
end
|
||||
end
|
|
@ -0,0 +1,129 @@
|
|||
|
||||
class TmuxFormatPrinter
|
||||
abstract class Shell
|
||||
abstract def exec(cmd)
|
||||
end
|
||||
|
||||
FORMAT_SEPARATOR = /[ ,]+/
|
||||
|
||||
COLOR_MAP = {
|
||||
black: 0,
|
||||
red: 1,
|
||||
green: 2,
|
||||
yellow: 3,
|
||||
blue: 4,
|
||||
magenta: 5,
|
||||
cyan: 6,
|
||||
white: 7
|
||||
}
|
||||
|
||||
LAYER_MAP = {
|
||||
bg: "setab",
|
||||
fg: "setaf"
|
||||
}
|
||||
|
||||
STYLE_MAP = {
|
||||
bright: "bold",
|
||||
bold: "bold",
|
||||
dim: "dim",
|
||||
underscore: "smul",
|
||||
reverse: "rev",
|
||||
italics: "sitm"
|
||||
}
|
||||
|
||||
class ShellExec < Shell
|
||||
def exec(cmd)
|
||||
`#{cmd}`.chomp
|
||||
end
|
||||
end
|
||||
|
||||
@shell : Shell
|
||||
@applied_styles : Hash(String, String)
|
||||
@reset_sequence : String | Nil
|
||||
|
||||
def initialize(shell = ShellExec.new)
|
||||
@shell = shell
|
||||
@applied_styles = {} of String => String
|
||||
end
|
||||
|
||||
def print(input, reset_styles_after = false)
|
||||
@applied_styles = {} of String => String
|
||||
|
||||
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]+)|.*))/)
|
||||
|
||||
return "" unless match
|
||||
|
||||
layer = match["layer"]
|
||||
color = match["color"]
|
||||
color_code = match["color_code"] if 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>.*)/)
|
||||
|
||||
return "" unless match
|
||||
|
||||
should_remove_style = match["remove"]? && match["remove"] == "no"
|
||||
style = match["style"]
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
def shell
|
||||
@shell
|
||||
end
|
||||
|
||||
#private
|
||||
|
||||
#attr_reader :shell
|
||||
end
|
|
@ -0,0 +1,6 @@
|
|||
version: 2.0
|
||||
shards:
|
||||
priority_queue:
|
||||
git: https://github.com/amadanmath/priority_queue.cr.git
|
||||
version: 0.1.0+git.commit.5ce7c82b876644f28715d384f3615a2dc7b2a66b
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
name: fingers
|
||||
version: 0.1.0
|
||||
|
||||
authors:
|
||||
- Jorge Morante <jorge@morante.eu>
|
||||
|
||||
targets:
|
||||
tmux-fingers:
|
||||
main: src/fingers.cr
|
||||
|
||||
dependencies:
|
||||
priority_queue:
|
||||
github: amadanmath/priority_queue.cr
|
||||
|
||||
crystal: 1.7.3
|
||||
|
||||
license: MIT
|
|
@ -0,0 +1,60 @@
|
|||
require "spec"
|
||||
require "../../../lib/fingers/hinter"
|
||||
|
||||
record StateDouble, selected_hints : Array(String)
|
||||
|
||||
class TextOutput < ::Fingers::Printer
|
||||
def initialize
|
||||
@contents = ""
|
||||
end
|
||||
|
||||
def print(msg)
|
||||
self.contents += msg
|
||||
end
|
||||
|
||||
def flush
|
||||
end
|
||||
|
||||
property :contents
|
||||
end
|
||||
|
||||
class TestFormatter < ::Fingers::Formatter
|
||||
def format(hint, highlight, selected = nil, offset = nil)
|
||||
"#{hint}#{highlight}"
|
||||
end
|
||||
end
|
||||
|
||||
describe Fingers::Hinter do
|
||||
input = "
|
||||
ola ke ase
|
||||
ke ase ola
|
||||
ke olaola ke
|
||||
ke ola ase
|
||||
|
||||
beep beep
|
||||
"
|
||||
|
||||
width = 40
|
||||
|
||||
output = TextOutput.new
|
||||
|
||||
formatter = TestFormatter.new
|
||||
|
||||
patterns = ["ola"]
|
||||
alphabet = "asdf".split("")
|
||||
|
||||
hinter = Fingers::Hinter.new(
|
||||
input: input,
|
||||
width: width,
|
||||
patterns: patterns,
|
||||
alphabet: alphabet,
|
||||
output: output,
|
||||
formatter: formatter,
|
||||
)
|
||||
|
||||
it "works" do
|
||||
hinter.run
|
||||
|
||||
puts output.contents
|
||||
end
|
||||
end
|
|
@ -0,0 +1,19 @@
|
|||
require "spec"
|
||||
require "../../../lib/fingers/input_socket"
|
||||
|
||||
describe Fingers::InputSocket do
|
||||
it "works" do
|
||||
spawn do
|
||||
sleep 1
|
||||
sender = Fingers::InputSocket.new
|
||||
sender.send_message("hey")
|
||||
end
|
||||
|
||||
listener = Fingers::InputSocket.new
|
||||
listener.on_input do |msg|
|
||||
msg.should eq("hey")
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
require "spec"
|
||||
require "../../../lib/fingers/match_formatter"
|
||||
|
||||
def setup(
|
||||
hint_format : String = "#[fg=yellow,bold]",
|
||||
highlight_format : String = "#[fg=yellow]",
|
||||
hint_position : String = "left",
|
||||
selected_hint_format : String= "#[fg=green,bold]",
|
||||
selected_highlight_format : String = "#[fg=green]",
|
||||
selected : Bool = false,
|
||||
offset : Tuple(Int32, Int32) | Nil = nil,
|
||||
hint : String = "a",
|
||||
highlight : String = "yolo",
|
||||
)
|
||||
formatter = Fingers::MatchFormatter.new(
|
||||
highlight_format: highlight_format,
|
||||
hint_format: hint_format,
|
||||
selected_highlight_format: selected_highlight_format,
|
||||
selected_hint_format: selected_hint_format,
|
||||
hint_position: hint_position,
|
||||
reset_sequence: "#[reset]"
|
||||
)
|
||||
|
||||
formatter.format(hint: hint, highlight: highlight, selected: selected, offset: offset)
|
||||
end
|
||||
|
||||
describe Fingers::MatchFormatter do
|
||||
context "when hint position" do
|
||||
context "is set to left" do
|
||||
it "places the hint on the left side" do
|
||||
result = setup(hint_position: "left")
|
||||
result.should eq("#[fg=yellow,bold]a#[fg=yellow]olo#[reset]")
|
||||
end
|
||||
end
|
||||
|
||||
context "is set to right" do
|
||||
it "places the hint on the right side" do
|
||||
result = setup(hint_position: "right")
|
||||
result.should eq("#[fg=yellow]yol#[fg=yellow,bold]a#[reset]")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when a hint is selected" do
|
||||
it "selects the correct format" do
|
||||
result = setup(selected: true)
|
||||
result.should eq("#[fg=green,bold]a#[fg=green]olo#[reset]")
|
||||
end
|
||||
end
|
||||
|
||||
context "when offset is provided" do
|
||||
it "only highlights at specified offset" do
|
||||
result = setup(offset: {1, 5}, highlight: "yoloyoloyolo", hint: "a")
|
||||
result.should eq("y#[fg=yellow,bold]a#[fg=yellow]loyo#[reset]loyolo")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,2 @@
|
|||
require "spec"
|
||||
require "../../../lib/fingers/view"
|
|
@ -0,0 +1,682 @@
|
|||
require "spec"
|
||||
require "../../lib/huffman"
|
||||
|
||||
expected_5 = [
|
||||
"s",
|
||||
"d",
|
||||
"f",
|
||||
"aa",
|
||||
"as",
|
||||
]
|
||||
|
||||
expected_50 = [
|
||||
"aaa",
|
||||
"aas",
|
||||
"aad",
|
||||
"aaf",
|
||||
"asa",
|
||||
"ass",
|
||||
"asd",
|
||||
"asf",
|
||||
"ada",
|
||||
"ads",
|
||||
"add",
|
||||
"adf",
|
||||
"afs",
|
||||
"afd",
|
||||
"aff",
|
||||
"saa",
|
||||
"sas",
|
||||
"sad",
|
||||
"saf",
|
||||
"ssa",
|
||||
"sss",
|
||||
"ssd",
|
||||
"ssf",
|
||||
"sda",
|
||||
"sds",
|
||||
"sdd",
|
||||
"sdf",
|
||||
"sfa",
|
||||
"afaa",
|
||||
"afas",
|
||||
"afad",
|
||||
"afaf",
|
||||
"sfsa",
|
||||
"sfss",
|
||||
"sfsd",
|
||||
"sfsf",
|
||||
"sfda",
|
||||
"sfds",
|
||||
"sfdd",
|
||||
"sfdf",
|
||||
"sffa",
|
||||
"sffs",
|
||||
"sfff",
|
||||
"sffda",
|
||||
"sffds",
|
||||
"sffdf",
|
||||
"sffdda",
|
||||
"sffdds",
|
||||
"sffddd",
|
||||
"sffddf",
|
||||
]
|
||||
|
||||
expected_alot = [
|
||||
"aaaa",
|
||||
"aaas",
|
||||
"aaaf",
|
||||
"aasa",
|
||||
"aass",
|
||||
"aasd",
|
||||
"aasf",
|
||||
"aada",
|
||||
"aads",
|
||||
"aadd",
|
||||
"aadf",
|
||||
"aafa",
|
||||
"aafs",
|
||||
"aafd",
|
||||
"aaff",
|
||||
"asaa",
|
||||
"asad",
|
||||
"asaf",
|
||||
"assa",
|
||||
"asss",
|
||||
"assd",
|
||||
"assf",
|
||||
"asda",
|
||||
"asds",
|
||||
"asdd",
|
||||
"asdf",
|
||||
"asfa",
|
||||
"asfs",
|
||||
"asfd",
|
||||
"asff",
|
||||
"adaa",
|
||||
"adad",
|
||||
"adaf",
|
||||
"adsa",
|
||||
"adss",
|
||||
"adsd",
|
||||
"adsf",
|
||||
"adda",
|
||||
"adds",
|
||||
"addd",
|
||||
"addf",
|
||||
"adfa",
|
||||
"adfs",
|
||||
"adfd",
|
||||
"adff",
|
||||
"afaa",
|
||||
"afas",
|
||||
"afaf",
|
||||
"afsa",
|
||||
"afss",
|
||||
"afsd",
|
||||
"afsf",
|
||||
"afda",
|
||||
"afds",
|
||||
"afdd",
|
||||
"afdf",
|
||||
"affa",
|
||||
"affs",
|
||||
"affd",
|
||||
"afff",
|
||||
"saaa",
|
||||
"saas",
|
||||
"saad",
|
||||
"sasa",
|
||||
"sass",
|
||||
"sasd",
|
||||
"sasf",
|
||||
"sada",
|
||||
"sads",
|
||||
"sadd",
|
||||
"sadf",
|
||||
"safa",
|
||||
"safs",
|
||||
"safd",
|
||||
"saff",
|
||||
"ssaa",
|
||||
"ssas",
|
||||
"ssaf",
|
||||
"sssa",
|
||||
"ssss",
|
||||
"sssd",
|
||||
"sssf",
|
||||
"ssda",
|
||||
"ssds",
|
||||
"ssdd",
|
||||
"ssdf",
|
||||
"ssfa",
|
||||
"ssfs",
|
||||
"ssfd",
|
||||
"ssff",
|
||||
"sdaa",
|
||||
"sdad",
|
||||
"sdaf",
|
||||
"sdsa",
|
||||
"sdss",
|
||||
"sdsd",
|
||||
"sdsf",
|
||||
"sdda",
|
||||
"sdds",
|
||||
"sddd",
|
||||
"sddf",
|
||||
"sdfa",
|
||||
"sdfs",
|
||||
"sdfd",
|
||||
"sdff",
|
||||
"sfaa",
|
||||
"sfad",
|
||||
"sfaf",
|
||||
"sfsa",
|
||||
"sfss",
|
||||
"sfsd",
|
||||
"sfsf",
|
||||
"sfda",
|
||||
"sfds",
|
||||
"sfdd",
|
||||
"sfdf",
|
||||
"sffa",
|
||||
"sffs",
|
||||
"sffd",
|
||||
"sfff",
|
||||
"daaa",
|
||||
"daas",
|
||||
"daad",
|
||||
"dasa",
|
||||
"dass",
|
||||
"dasd",
|
||||
"dasf",
|
||||
"dada",
|
||||
"dads",
|
||||
"dadd",
|
||||
"dadf",
|
||||
"dafa",
|
||||
"dafs",
|
||||
"dafd",
|
||||
"daff",
|
||||
"dsaa",
|
||||
"dsad",
|
||||
"dsaf",
|
||||
"dssa",
|
||||
"dsss",
|
||||
"dssd",
|
||||
"dssf",
|
||||
"dsda",
|
||||
"dsds",
|
||||
"dsdd",
|
||||
"dsdf",
|
||||
"dsfa",
|
||||
"dsfs",
|
||||
"dsfd",
|
||||
"dsff",
|
||||
"ddas",
|
||||
"ddad",
|
||||
"ddaf",
|
||||
"ddsa",
|
||||
"ddss",
|
||||
"ddsd",
|
||||
"ddsf",
|
||||
"ddda",
|
||||
"ddds",
|
||||
"dddd",
|
||||
"dddf",
|
||||
"ddfa",
|
||||
"ddfs",
|
||||
"ddfd",
|
||||
"ddff",
|
||||
"dfaa",
|
||||
"dfas",
|
||||
"dfaf",
|
||||
"dfsa",
|
||||
"dfss",
|
||||
"dfsd",
|
||||
"aaada",
|
||||
"aaads",
|
||||
"aaadd",
|
||||
"aaadf",
|
||||
"asasa",
|
||||
"asasd",
|
||||
"asasf",
|
||||
"adasa",
|
||||
"adass",
|
||||
"adasd",
|
||||
"adasf",
|
||||
"afada",
|
||||
"afads",
|
||||
"afadd",
|
||||
"afadf",
|
||||
"saafa",
|
||||
"saafs",
|
||||
"saafd",
|
||||
"saaff",
|
||||
"ssada",
|
||||
"ssads",
|
||||
"ssadf",
|
||||
"sdasa",
|
||||
"sdass",
|
||||
"sdasd",
|
||||
"sdasf",
|
||||
"sfasa",
|
||||
"sfass",
|
||||
"sfasd",
|
||||
"sfasf",
|
||||
"daafa",
|
||||
"daafs",
|
||||
"daafd",
|
||||
"daaff",
|
||||
"dsasa",
|
||||
"dsasd",
|
||||
"dsasf",
|
||||
"ddaaa",
|
||||
"ddaas",
|
||||
"ddaad",
|
||||
"ddaaf",
|
||||
"dfada",
|
||||
"dfads",
|
||||
"dfadd",
|
||||
"dfadf",
|
||||
"dfsfa",
|
||||
"dfsfs",
|
||||
"dfsfd",
|
||||
"dfsff",
|
||||
"dfdas",
|
||||
"dfdad",
|
||||
"dfdaf",
|
||||
"dfdsa",
|
||||
"dfdss",
|
||||
"dfdsd",
|
||||
"dfdsf",
|
||||
"dfdda",
|
||||
"dfdds",
|
||||
"dfddd",
|
||||
"dfddf",
|
||||
"dfdfa",
|
||||
"dfdfs",
|
||||
"dfdfd",
|
||||
"dfdff",
|
||||
"dffaa",
|
||||
"dffad",
|
||||
"dffaf",
|
||||
"dffsa",
|
||||
"dffss",
|
||||
"dffsd",
|
||||
"dffsf",
|
||||
"dffda",
|
||||
"dffds",
|
||||
"dffdd",
|
||||
"dffdf",
|
||||
"dfffa",
|
||||
"dfffs",
|
||||
"dfffd",
|
||||
"dffff",
|
||||
"faaaa",
|
||||
"faaas",
|
||||
"faaaf",
|
||||
"faasa",
|
||||
"faass",
|
||||
"faasd",
|
||||
"faasf",
|
||||
"faada",
|
||||
"faads",
|
||||
"faadd",
|
||||
"faadf",
|
||||
"faafa",
|
||||
"faafs",
|
||||
"faafd",
|
||||
"faaff",
|
||||
"fasaa",
|
||||
"fasad",
|
||||
"fasaf",
|
||||
"fassa",
|
||||
"fasss",
|
||||
"fassd",
|
||||
"fassf",
|
||||
"fasda",
|
||||
"fasds",
|
||||
"fasdd",
|
||||
"fasdf",
|
||||
"fasfa",
|
||||
"fasfs",
|
||||
"fasfd",
|
||||
"fasff",
|
||||
"fadas",
|
||||
"fadad",
|
||||
"fadaf",
|
||||
"fadsa",
|
||||
"fadss",
|
||||
"fadsd",
|
||||
"fadsf",
|
||||
"fadda",
|
||||
"fadds",
|
||||
"faddd",
|
||||
"faddf",
|
||||
"fadfa",
|
||||
"fadfs",
|
||||
"fadfd",
|
||||
"fadff",
|
||||
"fafaa",
|
||||
"fafad",
|
||||
"fafaf",
|
||||
"fafsa",
|
||||
"fafss",
|
||||
"fafsd",
|
||||
"fafsf",
|
||||
"fafda",
|
||||
"fafds",
|
||||
"fafdd",
|
||||
"fafdf",
|
||||
"faffa",
|
||||
"faffs",
|
||||
"faffd",
|
||||
"fafff",
|
||||
"fsaaa",
|
||||
"fsaas",
|
||||
"fsaaf",
|
||||
"fsasa",
|
||||
"fsass",
|
||||
"fsasd",
|
||||
"fsasf",
|
||||
"fsada",
|
||||
"fsads",
|
||||
"fsadd",
|
||||
"fsadf",
|
||||
"fsafa",
|
||||
"fsafs",
|
||||
"fsafd",
|
||||
"fsaff",
|
||||
"fssaa",
|
||||
"fssas",
|
||||
"fssad",
|
||||
"fsssa",
|
||||
"fssss",
|
||||
"fsssd",
|
||||
"fsssf",
|
||||
"fssda",
|
||||
"fssds",
|
||||
"fssdd",
|
||||
"fssdf",
|
||||
"fssfa",
|
||||
"fssfs",
|
||||
"fssfd",
|
||||
"fssff",
|
||||
"fsdas",
|
||||
"fsdad",
|
||||
"fsdaf",
|
||||
"fsdsa",
|
||||
"fsdss",
|
||||
"fsdsd",
|
||||
"fsdsf",
|
||||
"fsdda",
|
||||
"fsdds",
|
||||
"fsddd",
|
||||
"fsddf",
|
||||
"fsdfa",
|
||||
"fsdfs",
|
||||
"fsdfd",
|
||||
"fsdff",
|
||||
"fsfaa",
|
||||
"fsfad",
|
||||
"fsfaf",
|
||||
"fsfsa",
|
||||
"fsfss",
|
||||
"fsfsd",
|
||||
"fsfsf",
|
||||
"fsfda",
|
||||
"fsfds",
|
||||
"fsfdd",
|
||||
"fsfdf",
|
||||
"fsffa",
|
||||
"fsffs",
|
||||
"fsffd",
|
||||
"fsfff",
|
||||
"fdaaa",
|
||||
"fdaas",
|
||||
"fdaaf",
|
||||
"fdasa",
|
||||
"fdass",
|
||||
"fdasd",
|
||||
"fdasf",
|
||||
"fdada",
|
||||
"fdads",
|
||||
"fdadd",
|
||||
"fdadf",
|
||||
"fdafa",
|
||||
"fdafs",
|
||||
"fdafd",
|
||||
"fdaff",
|
||||
"fdsaa",
|
||||
"fdsas",
|
||||
"fdsaf",
|
||||
"fdssa",
|
||||
"fdsss",
|
||||
"fdssd",
|
||||
"fdssf",
|
||||
"fdsda",
|
||||
"fdsds",
|
||||
"fdsdd",
|
||||
"fdsdf",
|
||||
"fdsfa",
|
||||
"fdsfs",
|
||||
"fdsfd",
|
||||
"fdsff",
|
||||
"fddaa",
|
||||
"fddad",
|
||||
"fddaf",
|
||||
"fddsa",
|
||||
"fddss",
|
||||
"fddsd",
|
||||
"fddsf",
|
||||
"fddda",
|
||||
"fddds",
|
||||
"fdddd",
|
||||
"fdddf",
|
||||
"fddfa",
|
||||
"fddfs",
|
||||
"fddfd",
|
||||
"fddff",
|
||||
"fdfaa",
|
||||
"fdfas",
|
||||
"fdfaf",
|
||||
"fdfsa",
|
||||
"fdfss",
|
||||
"fdfsd",
|
||||
"fdfsf",
|
||||
"fdfda",
|
||||
"fdfds",
|
||||
"fdfdd",
|
||||
"fdfdf",
|
||||
"fdffa",
|
||||
"fdffs",
|
||||
"fdffd",
|
||||
"fdfff",
|
||||
"ffaaa",
|
||||
"ffaas",
|
||||
"ffaaf",
|
||||
"ffasa",
|
||||
"ffass",
|
||||
"ffasd",
|
||||
"ffasf",
|
||||
"ffada",
|
||||
"ffads",
|
||||
"ffadd",
|
||||
"ffadf",
|
||||
"ffafa",
|
||||
"ffafs",
|
||||
"ffafd",
|
||||
"ffaff",
|
||||
"ffsas",
|
||||
"ffsad",
|
||||
"ffsaf",
|
||||
"ffssa",
|
||||
"ffsss",
|
||||
"ffssd",
|
||||
"ffssf",
|
||||
"ffsda",
|
||||
"ffsds",
|
||||
"ffsdd",
|
||||
"ffsdf",
|
||||
"ffsfa",
|
||||
"ffsfs",
|
||||
"ffsfd",
|
||||
"ffsff",
|
||||
"ffdas",
|
||||
"ffdad",
|
||||
"ffdaf",
|
||||
"ffdsa",
|
||||
"ffdss",
|
||||
"ffdsd",
|
||||
"ffdsf",
|
||||
"ffdda",
|
||||
"ffdds",
|
||||
"ffddd",
|
||||
"ffddf",
|
||||
"ffdfa",
|
||||
"ffdfs",
|
||||
"ffdfd",
|
||||
"ffdff",
|
||||
"fffaa",
|
||||
"fffad",
|
||||
"fffaf",
|
||||
"fffsa",
|
||||
"fffss",
|
||||
"fffsd",
|
||||
"fffsf",
|
||||
"fffda",
|
||||
"fffds",
|
||||
"fffdd",
|
||||
"fffdf",
|
||||
"ffffa",
|
||||
"ffffs",
|
||||
"ffffd",
|
||||
"fffff",
|
||||
"asassa",
|
||||
"asasss",
|
||||
"asassd",
|
||||
"asassf",
|
||||
"ssadda",
|
||||
"ssadds",
|
||||
"ssaddd",
|
||||
"ssaddf",
|
||||
"dsassa",
|
||||
"dsassd",
|
||||
"dsassf",
|
||||
"dfdaaa",
|
||||
"dfdaas",
|
||||
"dfdaad",
|
||||
"dfdaaf",
|
||||
"dffasa",
|
||||
"dffass",
|
||||
"dffasd",
|
||||
"dffasf",
|
||||
"faaada",
|
||||
"faaads",
|
||||
"faaadd",
|
||||
"faaadf",
|
||||
"fasasa",
|
||||
"fasasd",
|
||||
"fasasf",
|
||||
"fadaaa",
|
||||
"fadaas",
|
||||
"fadaad",
|
||||
"fadaaf",
|
||||
"fafasa",
|
||||
"fafass",
|
||||
"fafasd",
|
||||
"fafasf",
|
||||
"fsaada",
|
||||
"fsaads",
|
||||
"fsaadd",
|
||||
"fsaadf",
|
||||
"fssafa",
|
||||
"fssafs",
|
||||
"fssafd",
|
||||
"fsdaaa",
|
||||
"fsdaas",
|
||||
"fsdaad",
|
||||
"fsdaaf",
|
||||
"fsfasa",
|
||||
"fsfass",
|
||||
"fsfasd",
|
||||
"fsfasf",
|
||||
"fdaada",
|
||||
"fdaads",
|
||||
"fdaadd",
|
||||
"fdaadf",
|
||||
"fdsada",
|
||||
"fdsads",
|
||||
"fdsadf",
|
||||
"fddasa",
|
||||
"fddass",
|
||||
"fddasd",
|
||||
"fddasf",
|
||||
"fdfada",
|
||||
"fdfads",
|
||||
"fdfadd",
|
||||
"fdfadf",
|
||||
"ffaada",
|
||||
"ffaads",
|
||||
"ffaadd",
|
||||
"ffaadf",
|
||||
"ffsaas",
|
||||
"ffsaad",
|
||||
"ffsaaf",
|
||||
"ffdaaa",
|
||||
"ffdaas",
|
||||
"ffdaad",
|
||||
"ffdaaf",
|
||||
"fffasa",
|
||||
"fffass",
|
||||
"fffasd",
|
||||
"fffasf",
|
||||
"dsasssa",
|
||||
"dsassss",
|
||||
"dsasssd",
|
||||
"dsasssf",
|
||||
"fasassa",
|
||||
"fasasss",
|
||||
"fasassd",
|
||||
"fasassf",
|
||||
"fssaffa",
|
||||
"fssaffs",
|
||||
"fssaffd",
|
||||
"fssafff",
|
||||
"fdsadda",
|
||||
"fdsadds",
|
||||
"fdsaddf",
|
||||
"ffsaaaa",
|
||||
"ffsaaas",
|
||||
"ffsaaad",
|
||||
"ffsaaaf",
|
||||
"fdsaddda",
|
||||
"fdsaddds",
|
||||
"fdsadddf",
|
||||
"fdsadddda",
|
||||
"fdsadddds",
|
||||
"fdsaddddd",
|
||||
"fdsaddddf",
|
||||
]
|
||||
|
||||
alphabet_a = ["a", "s", "d", "f"]
|
||||
alphabet_b = ["a", "s", "d", "f", "j", "k", "l", "g", "h"]
|
||||
|
||||
|
||||
describe Huffman do
|
||||
it "should work for 5" do
|
||||
huffman = Huffman.new
|
||||
|
||||
result = huffman.generate_hints(alphabet = alphabet_a, n = 5)
|
||||
result.should eq expected_5
|
||||
end
|
||||
|
||||
it "should work for 100" do
|
||||
huffman = Huffman.new
|
||||
|
||||
result = huffman.generate_hints(alphabet = alphabet_a, n = 50)
|
||||
result.should eq expected_50
|
||||
# TODO make sure priority queue is popping the same elements
|
||||
end
|
||||
end
|
|
@ -0,0 +1,14 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Huffman do
|
||||
it 'transforms tmux status line format into escape sequences' do
|
||||
huffman = Huffman.new
|
||||
|
||||
puts huffman.generate_hints(alphabet: "asdf".split(""), n: 5)
|
||||
puts "----"
|
||||
puts huffman.generate_hints(alphabet: "asdf".split(""), n: 50)
|
||||
puts "----"
|
||||
#puts huffman.generate_hints(alphabet: ["a", "s", "d", "f", "j", "k", "l", "g", "h"], n: 595)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
require "spec"
|
||||
require "../../lib/priority_queue"
|
||||
|
||||
describe PriorityQueue do
|
||||
it "transforms tmux status line format into escape sequences" do
|
||||
test = [
|
||||
[6, "drink tea"],
|
||||
[3, "Clear drains"],
|
||||
[4, "Feed cat"],
|
||||
[5, "Make tea"],
|
||||
[6, "eat biscuit"],
|
||||
[1, "Solve RC tasks"],
|
||||
[2, "Tax return"],
|
||||
]
|
||||
|
||||
results = [] of String
|
||||
|
||||
pq = PriorityQueue(String).new
|
||||
test.each do |pair|
|
||||
pr, str = pair
|
||||
pq.push(pr.to_i, str.to_s)
|
||||
end
|
||||
until pq.empty?
|
||||
results.push(pq.pop)
|
||||
end
|
||||
|
||||
expected = [
|
||||
"Solve RC tasks",
|
||||
"Tax return",
|
||||
"Clear drains",
|
||||
"Feed cat",
|
||||
"Make tea",
|
||||
"drink tea",
|
||||
"eat biscuit",
|
||||
]
|
||||
|
||||
results.should eq expected
|
||||
end
|
||||
end
|
|
@ -0,0 +1,27 @@
|
|||
require "spec"
|
||||
require "../../lib/tmux_format_printer"
|
||||
|
||||
class FakeShell < TmuxFormatPrinter::Shell
|
||||
def exec(cmd)
|
||||
"$(#{cmd})"
|
||||
end
|
||||
end
|
||||
|
||||
describe TmuxFormatPrinter do
|
||||
it "transforms tmux status line format into escape sequences" do
|
||||
printer = TmuxFormatPrinter.new(shell = FakeShell.new)
|
||||
result = printer.print("bg=red,fg=yellow,bold", reset_styles_after: true)
|
||||
expected = "$(tput setab 1)$(tput setaf 3)$(tput bold)$(tput sgr0)"
|
||||
|
||||
result.should eq expected
|
||||
end
|
||||
|
||||
it "transforms tmux status line format into escape sequences" do
|
||||
printer = TmuxFormatPrinter.new(shell = FakeShell.new)
|
||||
result = printer.print("bg=red,fg=yellow,bold", reset_styles_after: true)
|
||||
expected = "$(tput setab 1)$(tput setaf 3)$(tput bold)$(tput sgr0)"
|
||||
|
||||
result.should eq expected
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,12 @@
|
|||
require "spec"
|
||||
require "../../lib/tmux"
|
||||
|
||||
describe Tmux do
|
||||
it "transforms tmux status line format into escape sequences" do
|
||||
tmux = Tmux.new
|
||||
|
||||
panes = tmux.panes
|
||||
puts panes
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
require "spec"
|
|
@ -0,0 +1,8 @@
|
|||
require "fingers/cli"
|
||||
|
||||
module Fingers
|
||||
VERSION = "0.1.0"
|
||||
|
||||
cli = Cli.new
|
||||
cli.run
|
||||
end
|
Loading…
Reference in New Issue