Skip to content

Commit

Permalink
feat: Add support for tmuxinator append
Browse files Browse the repository at this point in the history
Adds support for tmuxinator append

Adds ability to specify default loading behavior (start or append)

- Use start when not possible to append

nits

nits and improvements

refactor Cli::bootstrap (#1)

Adds reference to the new command

use say

cli: Adds tests for bootstraping with option set to append

adds tests for project window index

refactor to use start --append instead of append. improve config loading

reduce complexity of the start method

reduce complexity in cli.rb

void(commit)

extract get params

Update lib/tmuxinator/config.rb

Co-authored-by: Noah Frederick <code@noahfrederick.com>

Update lib/tmuxinator/project.rb

Co-authored-by: Noah Frederick <code@noahfrederick.com>

Update lib/tmuxinator/cli.rb

Co-authored-by: Noah Frederick <code@noahfrederick.com>
Signed-off-by: Andrew Kofink <ajkofink@gmail.com>
  • Loading branch information
2 people authored and akofink committed May 30, 2024
1 parent 0f7e9ff commit 394dda3
Show file tree
Hide file tree
Showing 15 changed files with 177 additions and 25 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
## Unreleased
### Features
- Add support for tmuxinator start --append

## 3.2.1
### Enhancements
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions lib/tmuxinator/assets/template.erb
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ cd <%= root || "." %>
# directory if required to support tmux < 1.9
<% if windows.first.root? %>
<% if Tmuxinator::Config.version < 1.9 %>
TMUX= <%= tmux_new_session_command %>
TMUX= <%= tmux_new_or_append_to_existing_session_command %>
<%= windows.first.tmux_window_command_prefix %> <%= "cd #{windows.first.root}".shellescape %> C-m
<%- else -%>
TMUX= <%= tmux_new_session_command %> -c <%= windows.first.root.shellescape %>
TMUX= <%= tmux_new_or_append_to_existing_session_command %> -c <%= windows.first.root.shellescape %>
<% end %>
<%- else -%>
TMUX= <%= tmux_new_session_command %>
TMUX= <%= tmux_new_or_append_to_existing_session_command %>
<% end %>
<% if Tmuxinator::Config.version < 1.7 %>
Expand Down Expand Up @@ -109,7 +109,7 @@ cd <%= root || "." %>
<%= hook_on_project_restart %>
<%- end -%>
<%- if attach? -%>
<%- if attach? && !in_current_session -%>
if [ -z "$TMUX" ]; then
<%= tmux %> -u attach-session -t <%= name %>
else
Expand Down
56 changes: 40 additions & 16 deletions lib/tmuxinator/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ def create_project(project_options = {})
force_attach: attach,
force_detach: detach,
name: project_options[:name],
project_config: project_options[:project_config]
project_config: project_options[:project_config],
current_session: project_options[:current_session]
}

begin
Expand Down Expand Up @@ -227,6 +228,40 @@ def show_continuation_prompt
def kill_project(project)
Kernel.exec(project.kill)
end

def tmux_session_exists
ENV["TMUX"] ? true : false
end

def should_append?(options)
options[:append] == true ||
(options[:append] != false && Tmuxinator::Config.options[:append])
end

def append_in_session(options, name = nil)
if should_append?(options) && !tmux_session_exists
say "Creating session '#{name}'"
true
end
end

def get_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"],
current_session: append_in_session(options, name)
}
end
end

desc "start [PROJECT] [ARGS]", COMMANDS[:start]
Expand All @@ -240,22 +275,11 @@ def kill_project(project)
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 = get_params(name, *args)

show_version_warning if version_warning?(
options["suppress-tmux-version-warning"]
Expand Down
10 changes: 10 additions & 0 deletions lib/tmuxinator/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ def home?
File.directory?(home)
end

def options_file
directory + ".yml"
end

# ~/.config/tmuxinator unless $XDG_CONFIG_HOME has been configured to use
# a custom value. (e.g. if $XDG_CONFIG_HOME is set to ~/my-config, the
# return value will be ~/my-config/tmuxinator)
Expand Down Expand Up @@ -63,6 +67,12 @@ def default
"#{directory}/default.yml"
end

def options
YAML.safe_load(File.read(options_file), aliases: true) || {}
rescue SyntaxError, StandardError
{}
end

def default?
exist?(name: "default")
end
Expand Down
28 changes: 26 additions & 2 deletions lib/tmuxinator/project.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ def initialize(yaml, options = {})

@force_attach = options[:force_attach]
@force_detach = options[:force_detach]
@current_session = options[:current_session]

raise "Cannot force_attach and force_detach at the same time" \
if @force_attach && @force_detach
Expand All @@ -107,6 +108,10 @@ def self.render_template(template, bndg)
bndg.eval(Erubi::Engine.new(content).src)
end

def in_current_session
@current_session || yaml["current_session"]
end

def windows
windows_yml = yaml["tabs"] || yaml["windows"]

Expand All @@ -121,6 +126,10 @@ def root
end

def name
in_current_session ? "" : full_name
end

def full_name
name = custom_name || yaml["project_name"] || yaml["name"]
blank?(name) ? nil : name.to_s.shellescape
end
Expand Down Expand Up @@ -209,7 +218,12 @@ def tmux_options
end

def base_index
get_base_index.to_i
base = 0
if in_current_session
# Get the last window index + 1
base = 1 + `tmux list-windows -F '#I'`.split.last.to_i
end
base + get_base_index.to_i
end

def pane_base_index
Expand Down Expand Up @@ -241,7 +255,7 @@ def name?
end

def window(index)
"#{name}:#{index}"
in_current_session ? ":#{index}" : "#{name}:#{index}"
end

def send_pane_command(cmd, window_index, _pane_index)
Expand Down Expand Up @@ -333,6 +347,16 @@ def tmux_new_session_command
"#{tmux} new-session -d -s #{name} #{window}"
end

# Returns the new session command or the new window command,
# depending on whether we're in a current session or not
def tmux_new_or_append_to_existing_session_command
if in_current_session
windows.first.tmux_new_window_command
else
tmux_new_session_command
end
end

def tmux_kill_session_command
"#{tmux} kill-session -t #{name}"
end
Expand Down
3 changes: 2 additions & 1 deletion lib/tmuxinator/window.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ def tmux_window_command_prefix
end

def tmux_window_name_option
name ? "-n #{name}" : ""
project_name = project.in_current_session ? "#{project.full_name}:" : ""
name ? "-n #{project_name}#{name}" : ""
end

def tmux_new_window_command
Expand Down
7 changes: 7 additions & 0 deletions spec/factories/projects.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,11 @@ def yaml_load(file)

initialize_with { Tmuxinator::Project.load(file) }
end

factory :append_in_current_session, class: Tmuxinator::Project do
transient do
file { yaml_load("spec/fixtures/sample_in_current_session.yml") }
end
initialize_with { Tmuxinator::Project.new(file) }
end
end
1 change: 1 addition & 0 deletions spec/fixtures/config_append.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
append: true
Empty file added spec/fixtures/empty.yml
Empty file.
5 changes: 5 additions & 0 deletions spec/fixtures/sample_in_current_session.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# ~/.tmuxinator/sample-in-current-session.yml
name: sample_in_current_session
current_session: true
windows:
- editor: echo sample-in-current-session
11 changes: 11 additions & 0 deletions spec/lib/tmuxinator/cli_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,17 @@
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
Expand Down
36 changes: 36 additions & 0 deletions spec/lib/tmuxinator/config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,42 @@
end
end

describe "#options" do
let(:not_found) { "#{fixtures_dir}/does_not_exist.yml" }
let(:empty) { "#{fixtures_dir}/empty.yml" }
let(:conf_append) { "#{fixtures_dir}/config_append.yml" }

context "with unknown file" do
before do
allow(described_class).to receive(:options_file).and_return not_found
end

it "loads the options" do
expect(described_class.options).not_to be_nil
end
end

context "with empty yml" do
before do
allow(described_class).to receive(:options_file).and_return empty
end

it "loads the options" do
expect(described_class.options).not_to be_nil
end
end

context "with append config" do
before do
allow(described_class).to receive(:options_file).and_return conf_append
end

it "loads the options" do
expect(described_class.options["append"]).to eq true
end
end
end

describe "#global_project" do
let(:directory) { described_class.directory }
let(:base) { "#{directory}/sample.yml" }
Expand Down
24 changes: 24 additions & 0 deletions spec/lib/tmuxinator/project_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
let(:project_with_force_detach) do
FactoryBot.build(:project_with_force_detach)
end
let(:append_in_current_session) do
FactoryBot.build(:append_in_current_session)
end

let(:wemux_project) { FactoryBot.build(:wemux_project) }
let(:noname_project) { FactoryBot.build(:noname_project) }
Expand Down Expand Up @@ -191,6 +194,13 @@
end
end

context "in current session" do
it "will render an empty name" do
rendered = append_in_current_session
expect(rendered.name).to eq ""
end
end

context "window as non-string literal" do
it "will gracefully handle a window name given as a non-string literal" do
rendered = project_with_literals_as_window_name
Expand Down Expand Up @@ -368,6 +378,20 @@
expect(project.base_index).to eq 0
end
end

context "in current session" do
before do
allow(append_in_current_session).
to receive_messages(get_base_index: "0")
allow_any_instance_of(Kernel).to receive(:`).
with(Regexp.new("tmux list-windows | awk '{print $1}'")).
and_return("2:\n")
end

it "should return the offseted index" do
expect(append_in_current_session.base_index).to eq 3
end
end
end

describe "#startup_window" do
Expand Down
6 changes: 4 additions & 2 deletions spec/lib/tmuxinator/window_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@
tmux: "tmux",
root: root,
root?: root?,
base_index: 1
base_index: 1,
in_current_session: false
)
end

Expand All @@ -64,7 +65,8 @@
base_index: 1,
pane_base_index: 0,
root: "/project/tmuxinator",
root?: true
root?: true,
in_current_session: false
)
end

Expand Down

0 comments on commit 394dda3

Please sign in to comment.