From 5cf576516029e56212d3cf30415fbd7f00dfd5fc Mon Sep 17 00:00:00 2001 From: Jorge Morante Date: Thu, 26 Oct 2023 13:01:44 +0200 Subject: [PATCH] refactoring load-config and Config.struct --- spec/lib/config_spec.cr | 125 +++++++++++ spec/lib/fingers/commands/load_config_spec.cr | 77 +++++++ spec/lib/tmux_style_printer_spec.cr | 2 +- src/fingers/cli.cr | 2 +- src/fingers/commands/load_config.cr | 139 ++++-------- src/fingers/config.cr | 206 +++++++++++++----- src/fingers/types.cr | 4 + src/persistent_shell.cr | 27 +++ src/tmux.cr | 28 +-- src/tmux_style_printer.cr | 6 +- 10 files changed, 433 insertions(+), 183 deletions(-) create mode 100644 spec/lib/config_spec.cr create mode 100644 spec/lib/fingers/commands/load_config_spec.cr create mode 100644 src/persistent_shell.cr diff --git a/spec/lib/config_spec.cr b/spec/lib/config_spec.cr new file mode 100644 index 0000000..c1c1571 --- /dev/null +++ b/spec/lib/config_spec.cr @@ -0,0 +1,125 @@ +require "../spec_helper" +require "../../src/fingers/config" + +describe Fingers::Config do + describe "errors" do + it "should valid when there are noerrors" do + conf = Fingers::Config.build + conf.valid?.should eq(true) + end + + it "should not be valid when there are errors" do + conf = Fingers::Config.build + conf.errors << "shit" + conf.valid?.should eq(false) + end + + it "errors should not be serialized" do + conf = Fingers::Config.build + has_errors = !!conf.to_json.match(/errors/) + has_errors.should eq(false) + end + end + + describe "keyboard-layout" do + it "is valid for known layouts" do + conf = Fingers::Config.build + conf.keyboard_layout = "qwerty" + conf.valid?.should eq(true) + end + + it "is should not include disallowed chars" do + conf = Fingers::Config.build + conf.keyboard_layout = "qwerty" + conf.alphabet.includes?("c").should eq(false) + conf.alphabet.includes?("i").should eq(false) + conf.alphabet.includes?("m").should eq(false) + conf.alphabet.includes?("q").should eq(false) + conf.alphabet.includes?("n").should eq(false) + end + + it "is not valid for unknown layouts" do + conf = Fingers::Config.build + conf.keyboard_layout = "potato" + conf.valid?.should eq(false) + end + + it "is qwerty by default" do + conf = Fingers::Config.build + conf.keyboard_layout.should eq("qwerty") + end + + it "populates alphabet" do + conf = Fingers::Config.build + conf.alphabet.empty?.should eq(false) + end + end + + describe "patterns" do + it "is valid for correct regexp" do + conf = Fingers::Config.build + conf.patterns = ["(foo|bar)"] + conf.valid?.should eq(true) + conf.patterns.size.should be > 0 + end + + it "is not valid for incorrect regexps" do + conf = Fingers::Config.build + conf.patterns = ["(unbalanced"] + conf.valid?.should eq(false) + end + + it "is empty by default" do + conf = Fingers::Config.build + conf.patterns.size.should eq(0) + end + end + + describe "styles" do + it "is valid for correct style" do + conf = Fingers::Config.build + conf.highlight_style = "fg=blue" + conf.valid?.should eq(true) + end + + it "is not valid for incorrect style" do + conf = Fingers::Config.build + conf.highlight_style = "fg=shit" + conf.valid?.should eq(false) + end + end + + describe "hint_position" do + it "is valid for correct value" do + conf = Fingers::Config.build + conf.hint_position = "left" + conf.valid?.should eq(true) + end + + it "is not valid for incorrect value" do + conf = Fingers::Config.build + conf.hint_position = "behind" + conf.valid?.should eq(false) + end + end + + describe "set_option" do + it "can set known options" do + conf = Fingers::Config.build + conf.set_option("keyboard_layout", "qwerty") + conf.valid?.should eq(true) + end + + it "can set known options with invalid values" do + conf = Fingers::Config.build + conf.set_option("keyboard_layout", "caca") + conf.valid?.should eq(false) + end + + it "is invalid when setting wrong option names" do + conf = Fingers::Config.build + conf.set_option("potato", "tomato") + conf.valid?.should eq(false) + end + end +end diff --git a/spec/lib/fingers/commands/load_config_spec.cr b/spec/lib/fingers/commands/load_config_spec.cr new file mode 100644 index 0000000..93e5703 --- /dev/null +++ b/spec/lib/fingers/commands/load_config_spec.cr @@ -0,0 +1,77 @@ +require "../../../spec_helper.cr" +require "../../../../src/fingers/commands/load_config" + + class FakeShell < Shell + @known_cmds = {} of String => String + + def exec(cmd) : String + output = @known_cmds[cmd]? + + return "" if cmd =~ /bind-key.*send-input/ + + if output.nil? + puts "Unknown cmd #{cmd}" + "" + else + output + end + end + + def expect(cmd, output) + @known_cmds[cmd] = output + end + + def clear! + @known_cmds = {} of String => String + end + end + +describe Fingers::Commands::LoadConfig do + it "can be instantiated" do + cmd = Fingers::Commands::LoadConfig.new(FakeShell.new) + end + + it "can run" do + shell = FakeShell.new + cmd = Fingers::Commands::LoadConfig.new(shell: shell, executable_path: "/path/to/fingers", log_path: "/tmp/log_path") + + shell.expect("tmux show-options -g | grep ^@fingers", "@fingers-key") + shell.expect("tmux show-option -gv @fingers-key", "F") + shell.expect("tmux -V", "3.3a") + shell.expect(%(tmux bind-key F run-shell -b "/path/to/fingers start '\#{pane_id}' self >>/tmp/log_path 2>&1"), "") + + cmd.run + end + + it "assigns options to config struct" do + shell = FakeShell.new + cmd = Fingers::Commands::LoadConfig.new(shell: shell, executable_path: "/path/to/fingers", log_path: "/tmp/log_path") + + shell.expect("tmux show-options -g | grep ^@fingers", "@fingers-key") + shell.expect("tmux show-option -gv @fingers-key", "A") + shell.expect("tmux -V", "3.3a") + shell.expect(%(tmux bind-key A run-shell -b "/path/to/fingers start '\#{pane_id}' self >>/tmp/log_path 2>&1"), "") + + cmd.run + Fingers.config.key.should eq("A") + end + + it "propagates config errors" do + shell = FakeShell.new + output = IO::Memory.new + cmd = Fingers::Commands::LoadConfig.new(shell: shell, executable_path: "/path/to/fingers", log_path: "/tmp/log_path", output: output) + + shell.expect("tmux show-options -g | grep ^@fingers", "@fingers-hint-style") + shell.expect("tmux show-option -gv @fingers-hint-style", "fg=caca") + shell.expect("tmux -V", "3.3a") + shell.expect(%(tmux bind-key F run-shell -b "/path/to/fingers start '\#{pane_id}' self >>/tmp/log_path 2>&1"), "") + shell.expect(%(tmux set-option -ug @fingers-hint-style), "") + + cmd.run + + output.rewind + + cmd.errors.empty?.should eq(false) + (output.gets || "").size.should be > 0 + end +end diff --git a/spec/lib/tmux_style_printer_spec.cr b/spec/lib/tmux_style_printer_spec.cr index 2a5cea2..43fabfd 100644 --- a/spec/lib/tmux_style_printer_spec.cr +++ b/spec/lib/tmux_style_printer_spec.cr @@ -1,7 +1,7 @@ require "spec" require "../../src/tmux_style_printer" -class FakeShell < TmuxStylePrinter::Shell +class FakeShell < Shell def exec(cmd) "$(#{cmd})" end diff --git a/src/fingers/cli.cr b/src/fingers/cli.cr index 2fea677..355e790 100644 --- a/src/fingers/cli.cr +++ b/src/fingers/cli.cr @@ -11,7 +11,7 @@ module Fingers when "start" Fingers::Commands::Start.new(args) when "load-config" - Fingers::Commands::LoadConfig.new(args) + Fingers::Commands::LoadConfig.new when "send-input" Fingers::Commands::SendInput.new(args) when "version" diff --git a/src/fingers/commands/load_config.cr b/src/fingers/commands/load_config.cr index fb90a70..b29383b 100644 --- a/src/fingers/commands/load_config.cr +++ b/src/fingers/commands/load_config.cr @@ -2,22 +2,30 @@ require "file_utils" require "./base" require "../dirs" require "../config" +require "../types" require "../../tmux" +require "../../persistent_shell" -class Fingers::Commands::LoadConfig < Fingers::Commands::Base +class Fingers::Commands::LoadConfig @fingers_options_names : Array(String) | Nil property config : Fingers::Config + property shell : Shell + property log_path : String + property executable_path : String + property errors : Array(String) = [] of String + property output : IO - DISALLOWED_CHARS = /cimqn/ - - def initialize(*args) - super(*args) - @config = Fingers::Config.new + def initialize( + @shell = PersistentShell.new, + @log_path = Fingers::Dirs::LOG_PATH.to_s, + @executable_path = Process.executable_path.to_s, + @output = STDOUT + ) + @config = Fingers::Config.build end def run - validate_options! parse_tmux_conf setup_bindings end @@ -31,75 +39,46 @@ class Fingers::Commands::LoadConfig < Fingers::Commands::Base Fingers.reset_config - config.tmux_version = `tmux -V`.chomp.split(" ").last + config.tmux_version = shell.exec("tmux -V").chomp.split(" ").last options.each do |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 "benchmark_mode" - config.benchmark_mode = value - when "hint_position" - config.hint_position = value - when "hint_style" - config.hint_style = tmux.parse_style(value) - when "selected_hint_style" - config.selected_hint_style = tmux.parse_style(value) - when "highlight_style" - config.highlight_style = tmux.parse_style(value) - when "backdrop_style" - config.backdrop_style = tmux.parse_style(value) - when "selected_highlight_style" - config.selected_highlight_style = tmux.parse_style(value) + if option.match(/pattern_[0-9]+/) + user_defined_patterns << value + next end - if option.match(/pattern/) - check_pattern!(value) - user_defined_patterns.push(value) + config.set_option(option, value) + + if !config.valid? + unset_tmux_option!(method_to_option(option)) + output.puts "Found errors #{config.errors}" + self.errors = config.errors.clone end end - config.patterns = clean_up_patterns([ + config.patterns = [ *enabled_default_patterns, *user_defined_patterns, - ]) + ] - config.alphabet = ::Fingers::Config::ALPHABET_MAP[Fingers.config.keyboard_layout].split("").reject do |char| - char.match(DISALLOWED_CHARS) + if !config.valid? + output.puts "Found errors #{config.errors}" + #exit(1) end config.save Fingers.reset_config - rescue e : TmuxStylePrinter::InvalidFormat - puts "[tmux-fingers] #{e.message}" - exit(1) - end - - def clean_up_patterns(patterns) - patterns.reject(&.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"` + shell.exec(%(tmux bind-key #{Fingers.config.key} run-shell -b "#{executable_path} start '\#{pane_id}' self >>#{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) + next if char.match(Fingers::Config::DISALLOWED_CHARS) fingers_mode_bind(char, "hint:#{char}:main") fingers_mode_bind(char.upcase, "hint:#{char}:shift") @@ -124,79 +103,43 @@ class Fingers::Commands::LoadConfig < Fingers::Commands::Base ::Fingers::Config::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 + options[option_method] = shell.exec(%(tmux show-option -gv #{option})).chomp end options end - def valid_option?(option) - option_method = option_to_method(option) - - @config.members.includes?(option_method) || option_method.match(/pattern_[0-9]+/) || option_method == "skip_wizard" - end - def fingers_options_names - @fingers_options_names ||= `tmux show-options -g | grep ^@fingers` + @fingers_options_names ||= shell.exec(%(tmux show-options -g | grep ^@fingers)) .chomp.split("\n") .map { |line| line.split(" ")[0] } .reject { |option| option.empty? } end def unset_tmux_option!(option) - `tmux set-option -ug #{option}` - end - - def check_pattern!(pattern) - begin - Regex.new(pattern) - rescue e: ArgumentError - puts "[tmux-fingers] Invalid pattern: #{pattern}" - puts "[tmux-fingers] #{e.message}" - exit(1) - end - 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) + shell.exec(%(tmux set-option -ug #{option})) 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}"` + def method_to_option(method) + "@fingers-#{method.tr("_", "-")}" end - def cli - Process.executable_path + def fingers_mode_bind(key, command) + shell.exec(%(tmux bind-key -Tfingers "#{key}" run-shell -b "#{executable_path} send-input #{command}")) end + def tmux - Tmux.new(`tmux -V`.chomp.split(" ").last) + Tmux.new(shell.exec("tmux -V").chomp.split(" ").last) end end diff --git a/src/fingers/config.cr b/src/fingers/config.cr index dbece23..834532f 100644 --- a/src/fingers/config.cr +++ b/src/fingers/config.cr @@ -1,42 +1,10 @@ require "json" +require "../tmux_style_printer" module Fingers struct Config - include JSON::Serializable - - property key : String - property keyboard_layout : String - property patterns : Array(String) - property alphabet : Array(String) - property benchmark_mode : String - property main_action : String - property ctrl_action : String - property alt_action : String - property shift_action : String - property hint_position : String - property hint_style : String - property selected_hint_style : String - property highlight_style : String - property selected_highlight_style : String - property backdrop_style : String - property tmux_version : String - FORMAT_PRINTER = TmuxStylePrinter.new - 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:///)[^\\s()\"]+)", - "path": "(([.\\w\\-~\\$@]+)?(/[.\\w\\-@]+)+/?)", - "hex": "(0x[0-9a-fA-F]+)", - "kubernetes": "(deployment.app|binding|componentstatuse|configmap|endpoint|event|limitrange|namespace|node|persistentvolumeclaim|persistentvolume|pod|podtemplate|replicationcontroller|resourcequota|secret|serviceaccount|service|mutatingwebhookconfiguration.admissionregistration.k8s.io|validatingwebhookconfiguration.admissionregistration.k8s.io|customresourcedefinition.apiextension.k8s.io|apiservice.apiregistration.k8s.io|controllerrevision.apps|daemonset.apps|deployment.apps|replicaset.apps|statefulset.apps|tokenreview.authentication.k8s.io|localsubjectaccessreview.authorization.k8s.io|selfsubjectaccessreviews.authorization.k8s.io|selfsubjectrulesreview.authorization.k8s.io|subjectaccessreview.authorization.k8s.io|horizontalpodautoscaler.autoscaling|cronjob.batch|job.batch|certificatesigningrequest.certificates.k8s.io|events.events.k8s.io|daemonset.extensions|deployment.extensions|ingress.extensions|networkpolicies.extensions|podsecuritypolicies.extensions|replicaset.extensions|networkpolicie.networking.k8s.io|poddisruptionbudget.policy|clusterrolebinding.rbac.authorization.k8s.io|clusterrole.rbac.authorization.k8s.io|rolebinding.rbac.authorization.k8s.io|role.rbac.authorization.k8s.io|storageclasse.storage.k8s.io)[[:alnum:]_#$%&+=/@-]+", - "git-status": "(modified|deleted|new file): +(?.+)", - "git-status-branch": "Your branch is up to date with '(?.*)'.", - "diff": "(---|\\+\\+\\+) [ab]/(?.*)", - } - ALPHABET_MAP = { "qwerty": "asdfqwerzxcvjklmiuopghtybn", "qwerty-homerow": "asdfjklgh", @@ -60,24 +28,154 @@ module Fingers "colemak-right-hand": "neioluymjhk", } - 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_style = FORMAT_PRINTER.print("fg=green,bold"), - @highlight_style = FORMAT_PRINTER.print("fg=yellow"), - @selected_hint_style = FORMAT_PRINTER.print("fg=blue,bold"), - @selected_highlight_style = FORMAT_PRINTER.print("fg=blue"), - @backdrop_style = "", - @tmux_version = "", - @benchmark_mode = "0" - ) + DISALLOWED_CHARS = /[cimqn]/ + + 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:///)[^\\s()\"]+)", + "path": "(([.\\w\\-~\\$@]+)?(/[.\\w\\-@]+)+/?)", + "hex": "(0x[0-9a-fA-F]+)", + "kubernetes": "(deployment.app|binding|componentstatuse|configmap|endpoint|event|limitrange|namespace|node|persistentvolumeclaim|persistentvolume|pod|podtemplate|replicationcontroller|resourcequota|secret|serviceaccount|service|mutatingwebhookconfiguration.admissionregistration.k8s.io|validatingwebhookconfiguration.admissionregistration.k8s.io|customresourcedefinition.apiextension.k8s.io|apiservice.apiregistration.k8s.io|controllerrevision.apps|daemonset.apps|deployment.apps|replicaset.apps|statefulset.apps|tokenreview.authentication.k8s.io|localsubjectaccessreview.authorization.k8s.io|selfsubjectaccessreviews.authorization.k8s.io|selfsubjectrulesreview.authorization.k8s.io|subjectaccessreview.authorization.k8s.io|horizontalpodautoscaler.autoscaling|cronjob.batch|job.batch|certificatesigningrequest.certificates.k8s.io|events.events.k8s.io|daemonset.extensions|deployment.extensions|ingress.extensions|networkpolicies.extensions|podsecuritypolicies.extensions|replicaset.extensions|networkpolicie.networking.k8s.io|poddisruptionbudget.policy|clusterrolebinding.rbac.authorization.k8s.io|clusterrole.rbac.authorization.k8s.io|rolebinding.rbac.authorization.k8s.io|role.rbac.authorization.k8s.io|storageclasse.storage.k8s.io)[[:alnum:]_#$%&+=/@-]+", + "git-status": "(modified|deleted|new file): +(?.+)", + "git-status-branch": "Your branch is up to date with '(?.*)'.", + "diff": "(---|\\+\\+\\+) [ab]/(?.*)", + } + + def self.build + from_json("{}") + end + + def self.alphabet_for(layout) + ALPHABET_MAP[layout].split("").reject { |char| char =~ DISALLOWED_CHARS } + end + + def self.parse_style(style) + FORMAT_PRINTER.print(style) + end + + include JSON::Serializable + + property key : String = "F" + + getter keyboard_layout : String = "qwerty" + def keyboard_layout=(value) + if !ALPHABET_MAP[value]? + errors << "Invalid layout #{value}" + return + end + + @keyboard_layout = value + end + + def alphabet + self.class.alphabet_for(keyboard_layout) + end + + getter patterns : Array(String) = [] of String + def patterns=(value) + value.each do |pattern| + error = Regex.error?(pattern) + if error + @errors << "Invalid regexp\n\t#{pattern}\n\t#{error}" + return + end + end + + @patterns = value + end + + getter highlight_style : String = parse_style("fg=green,bold") + def highlight_style=(value) + parsed_style = parse_style!(value) + @highlight_style if parsed_style + end + + def parse_style!(style) + begin + self.class.parse_style(style) + rescue TmuxStylePrinter::InvalidFormat + @errors << "Invalid style: #{style}" + end + end + + getter highlight_style : String = parse_style("fg=yellow") + def highlight_style=(value) + parsed_style = parse_style!(value) + @highlight_style if parsed_style + end + + getter hint_style : String = parse_style("fg=green,bold") + def hint_style=(value) + parsed_style = parse_style!(value) + @hint_style if parsed_style + end + + getter selected_highlight_style : String = parse_style("fg=blue") + def highlight_style=(value) + parsed_style = parse_style!(value) + @highlight_style if parsed_style + end + + getter selected_hint_style : String = parse_style("fg=blue,bold") + def hint_style=(value) + parsed_style = parse_style!(value) + @hint_style if parsed_style + end + + getter backdrop_style : String = "" + def hint_style=(value) + parsed_style = parse_style!(value) + @backdrop_style if parsed_style + end + + def parse_style!(style) + begin + self.class.parse_style(style) + rescue TmuxStylePrinter::InvalidFormat + @errors << "Invalid style: #{style}" + end + end + + property tmux_version : String = "" + property main_action : String = ":copy:" + property ctrl_action : String = ":open:" + property alt_action : String = "" + property shift_action : String = ":paste: " + + getter hint_position : String = "left" + def hint_position=(value) + if !["left", "right"].includes?(value) + @errors << "Invalid hint_position #{value}" + end + @hint_position = value + end + + property benchmark_mode : String = "0" + property skip_wizard : String = "0" + + @[JSON::Field(ignore: true)] + property errors : Array(String) = [] of String + + def valid? + errors.empty? + end + + macro define_set_option + def set_option(option : String, value : String | Array(String)) + case option + {% for method in @type.methods %} + {% if method.name.split("").last == "=" && method.name != "patterns=" && method.name != "errors=" %} + when "{{method.name.gsub(/=$/, "")}}" + self.{{method.name}} value + {% end %} + {% end %} + else + errors << "#{option} is not a valid option" + end + end end def self.load_from_cache @@ -91,12 +189,16 @@ module Fingers def members : Array(String) JSON.parse(to_json).as_h.keys end + + macro finished + define_set_option + end end def self.config @@config ||= Config.load_from_cache rescue - @@config ||= Config.new + @@config ||= Config.build end def self.reset_config diff --git a/src/fingers/types.cr b/src/fingers/types.cr index 8c8b7c9..4e3e2a3 100644 --- a/src/fingers/types.cr +++ b/src/fingers/types.cr @@ -8,3 +8,7 @@ module Fingers abstract def format(hint : String, highlight : String, selected : Bool, offset : Tuple(Int32, Int32) | Nil) end end + +abstract class Shell + abstract def exec(cmd) +end diff --git a/src/persistent_shell.cr b/src/persistent_shell.cr new file mode 100644 index 0000000..a881e7e --- /dev/null +++ b/src/persistent_shell.cr @@ -0,0 +1,27 @@ +require "./fingers/types" + +class PersistentShell < Shell + def initialize + @sh = Process.new("/bin/sh", input: :pipe, output: :pipe, error: :close) + end + + def exec(cmd) + ch = Channel(String).new + + spawn do + output = "" + while line = @sh.output.read_line + break if line == "cmd-end" + + output += "#{line}\n" + end + + ch.send(output) + end + + @sh.input.print("#{cmd}; echo cmd-end\n") + @sh.input.flush + output = ch.receive + output + end +end diff --git a/src/tmux.cr b/src/tmux.cr index 910b1d5..bca6579 100644 --- a/src/tmux.cr +++ b/src/tmux.cr @@ -39,32 +39,6 @@ end # rubocop:disable Metrics/ClassLength class Tmux - class Shell - def initialize - @sh = Process.new("/bin/sh", input: :pipe, output: :pipe, error: :close) - end - - def exec(cmd) - ch = Channel(String).new - - spawn do - output = "" - while line = @sh.output.read_line - break if line == "cmd-end" - - output += "#{line}\n" - end - - ch.send(output) - end - - @sh.input.print("#{cmd}; echo cmd-end\n") - @sh.input.flush - output = ch.receive - output - end - end - struct Pane include JSON::Serializable @@ -130,7 +104,7 @@ class Tmux end def initialize(version_string) - @sh = Shell.new + @sh = PersistentShell.new @version = Tmux.tmux_version_to_semver(version_string) end diff --git a/src/tmux_style_printer.cr b/src/tmux_style_printer.cr index dffc3ca..c2bc73c 100644 --- a/src/tmux_style_printer.cr +++ b/src/tmux_style_printer.cr @@ -1,12 +1,10 @@ +require "./fingers/types" + class TmuxStylePrinter class InvalidFormat < Exception end - abstract class Shell - abstract def exec(cmd) - end - STYLE_SEPARATOR = /[ ,]+/ COLOR_MAP = {