diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e33dad..a5eae49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ ## Unreleased +### Features +- Add support for tmuxinator start --append ## 3.3.3 ### Features diff --git a/README.md b/README.md index 121be4f..095c037 100644 --- a/README.md +++ b/README.md @@ -398,6 +398,11 @@ Shows tmuxinator's version. tmuxinator version ``` +Append a project's windows to the current session (instead of creating a new session) +``` +tmuxinator start [project] --append +``` + ## Project Configuration Location Using environment variables, it's possible to define which directory diff --git a/lib/tmuxinator/assets/template.erb b/lib/tmuxinator/assets/template.erb index f0c770e..f8a17b9 100644 --- a/lib/tmuxinator/assets/template.erb +++ b/lib/tmuxinator/assets/template.erb @@ -1,17 +1,19 @@ #!<%= ENV["SHELL"] || "/bin/bash" %> -# Clear rbenv variables before starting tmux -unset RBENV_VERSION -unset RBENV_DIR +<%- if !append? -%> + # Clear rbenv variables before starting tmux + unset RBENV_VERSION + unset RBENV_DIR -<%= tmux %> start-server; + <%= tmux %> start-server; +<%- end -%> cd <%= root || "." %> # Run on_project_start command. <%= hook_on_project_start %> -<%- if !tmux_has_session? name -%> +<%- if append? || !tmux_has_session?(name) -%> # Run pre command. <%= pre %> @@ -98,7 +100,7 @@ cd <%= root || "." %> <%= hook_on_project_restart %> <%- end -%> -<%- if attach? -%> +<%- if attach? && !append? -%> if [ -z "$TMUX" ]; then <%= tmux %> -u attach-session -t <%= name %> else diff --git a/lib/tmuxinator/cli.rb b/lib/tmuxinator/cli.rb index c26cf43..b7794db 100644 --- a/lib/tmuxinator/cli.rb +++ b/lib/tmuxinator/cli.rb @@ -190,7 +190,8 @@ module Tmuxinator force_attach: attach, force_detach: detach, name: project_options[:name], - project_config: project_options[:project_config] + project_config: project_options[:project_config], + append: project_options[:append] } begin @@ -227,6 +228,24 @@ module Tmuxinator def kill_project(project) Kernel.exec(project.kill) end + + def start_params(name = nil, *args) + # project-config takes precedence over a named project in the case that + # both are provided. + if options["project-config"] + args.unshift name if name + name = nil + end + + { + args: args, + attach: options[:attach], + custom_name: options[:name], + name: name, + project_config: options["project-config"], + append: options["append"], + } + end end desc "start [PROJECT] [ARGS]", COMMANDS[:start] @@ -240,22 +259,11 @@ module Tmuxinator desc: "Path to project config file" method_option "suppress-tmux-version-warning", desc: "Don't show a warning for unsupported tmux versions" - + method_option :append, type: :boolean, + desc: "Appends the project windows and panes in " \ + "the current session" def start(name = nil, *args) - # project-config takes precedence over a named project in the case that - # both are provided. - if options["project-config"] - args.unshift name if name - name = nil - end - - params = { - args: args, - attach: options[:attach], - custom_name: options[:name], - name: name, - project_config: options["project-config"] - } + params = start_params(name, *args) show_version_warning if version_warning?( options["suppress-tmux-version-warning"] @@ -312,24 +320,15 @@ module Tmuxinator desc: "Give the session a different name" method_option "project-config", aliases: "-p", desc: "Path to project config file" + method_option :append, type: :boolean, + desc: "Appends the project windows and panes in " \ + "the current session" def debug(name = nil, *args) - # project-config takes precedence over a named project in the case that - # both are provided. - if options["project-config"] - args.unshift name if name - name = nil - end - - params = { - args: args, - attach: options[:attach], - custom_name: options[:name], - name: name, - project_config: options["project-config"] - } + params = start_params(name, *args) project = create_project(params) + say project.render end diff --git a/lib/tmuxinator/project.rb b/lib/tmuxinator/project.rb index 7533fe0..dfecd90 100644 --- a/lib/tmuxinator/project.rb +++ b/lib/tmuxinator/project.rb @@ -87,13 +87,23 @@ module Tmuxinator @force_attach = options[:force_attach] @force_detach = options[:force_detach] + @append = options[:append] - raise "Cannot force_attach and force_detach at the same time" \ - if @force_attach && @force_detach + validate_options extend Tmuxinator::WemuxSupport if wemux? end + def validate_options + if @force_attach && @force_detach + raise "Cannot force_attach and force_detach at the same time" + end + + if append? && !tmux_has_session?(name) + raise "Cannot append to a session that does not exist" + end + end + def render self.class.render_template(Tmuxinator::Config.template, binding) end @@ -120,11 +130,25 @@ module Tmuxinator blank?(root) ? nil : File.expand_path(root).shellescape end + def current_session_name + `[[ -n "${TMUX+set}" ]] && tmux display-message -p "#S"`.strip + end + def name - name = custom_name || yaml["project_name"] || yaml["name"] + name = + if append? + current_session_name + else + custom_name || yaml["project_name"] || yaml["name"] + end + blank?(name) ? nil : name.to_s.shellescape end + def append? + @append + end + def pre pre_config = yaml["pre"] parsed_parameters(pre_config) @@ -167,6 +191,8 @@ module Tmuxinator end def tmux_has_session?(name) + return false unless name + # Redirect stderr to /dev/null in order to prevent "failed to connect # to server: Connection refused" error message and non-zero exit status # if no tmux sessions exist. @@ -208,7 +234,13 @@ module Tmuxinator end end + def last_window_index + `tmux list-windows -F '#I'`.split.last.to_i + end + def base_index + return last_window_index + 1 if append? + get_base_index.to_i end @@ -241,7 +273,7 @@ module Tmuxinator end def window(index) - "#{name}:#{index}" + append? ? ":#{index}" : "#{name}:#{index}" end def send_pane_command(cmd, window_index, _pane_index) @@ -329,6 +361,8 @@ module Tmuxinator end def tmux_new_session_command + return if append? + window = windows.first.tmux_window_name_option "#{tmux} new-session -d -s #{name} #{window}" end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 460da04..87b2053 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -115,4 +115,12 @@ FactoryBot.define do initialize_with { Tmuxinator::Project.load(file) } end + + factory :project_with_append, class: Tmuxinator::Project do + transient do + file { "spec/fixtures/sample.yml" } + end + + initialize_with { Tmuxinator::Project.load(file, append: true) } + end end diff --git a/spec/lib/tmuxinator/cli_spec.rb b/spec/lib/tmuxinator/cli_spec.rb index 8426aba..494b1ca 100644 --- a/spec/lib/tmuxinator/cli_spec.rb +++ b/spec/lib/tmuxinator/cli_spec.rb @@ -104,6 +104,17 @@ describe Tmuxinator::Cli do expect(instance).to receive(:start).with(*args) subject end + + context "and the append option is passed" do + let(:args) { ["sample", "--append"] } + + it "should call #start" do + instance = instance_double(cli) + expect(cli).to receive(:new).and_return(instance) + expect(instance).to receive(:start).with("sample", "--append") + subject + end + end end context "a thor command" do diff --git a/spec/lib/tmuxinator/project_spec.rb b/spec/lib/tmuxinator/project_spec.rb index 4c42ea1..8ef506c 100644 --- a/spec/lib/tmuxinator/project_spec.rb +++ b/spec/lib/tmuxinator/project_spec.rb @@ -37,6 +37,10 @@ describe Tmuxinator::Project do FactoryBot.build(:project_with_alias) end + let(:project_with_append) do + FactoryBot.build(:project_with_append) + end + it "should include Hooks" do expect(project).to be_kind_of(Tmuxinator::Hooks::Project) end @@ -46,6 +50,21 @@ describe Tmuxinator::Project do it "creates an instance" do expect(project).to be_a(Tmuxinator::Project) end + + it "validates append outside a current session" do + Tmuxinator::Project.any_instance.stub(tmux_has_session?: false) + expect { project_with_append }.to( + raise_error( + RuntimeError, + "Cannot append to a session that does not exist" + ) + ) + end + + it "validates append within a current session" do + Tmuxinator::Project.any_instance.stub(tmux_has_session?: true) + expect { project_with_append }.to_not raise_error + end end end @@ -368,6 +387,17 @@ describe Tmuxinator::Project do expect(project.base_index).to eq 0 end end + + context "with append set" do + before do + Tmuxinator::Project.any_instance.stub(tmux_has_session?: true) + project_with_append.stub(last_window_index: 3) + end + + it "defaults to the next window index" do + expect(project_with_append.base_index).to eq 4 + end + end end describe "#startup_window" do @@ -410,6 +440,13 @@ describe Tmuxinator::Project do it "gets the window and index for tmux" do expect(project.window(1)).to eq "sample:1" end + + context "with append set" do + it "excludes the session name" do + Tmuxinator::Project.any_instance.stub(tmux_has_session?: true) + expect(project_with_append.window(1)).to eq ":1" + end + end end describe "#name?" do @@ -576,6 +613,13 @@ describe Tmuxinator::Project do expect(project.tmux_new_session_command).to eq command end end + + context "with append set" do + it "returns nothing" do + Tmuxinator::Project.any_instance.stub(tmux_has_session?: true) + expect(project_with_append.tmux_new_session_command).to be_nil + end + end end describe "tmux_kill_session_command" do