Triggering tmux prefix via zsh script in normal mode? - shell

I've been setting up my terminal and I have zsh-vi-mode which gives me modal editing at the command line. I live in tmux, and I recently had the idea of replacing the tmux prefix with my vim leader key (space) when in normal mode in zsh.
Essentially, what I'd like to be able to do is alias space to ctrl+b (the tmux prefix) only when in normal mode.
I've been able to somewhat accomplish this with the following in my .zshrc:
function vim_to_tmux_leader() {
osascript -e 'tell application "System Events" to keystroke "b" using {control down}'
}
# The plugin will auto execute this zvm_after_lazy_keybindings function
function zvm_after_lazy_keybindings() {
zvm_define_widget vim_to_tmux_leader
# In normal mode, press Ctrl-B to access tmux
zvm_bindkey vicmd ' ' vim_to_tmux_leader
}
This is clearly an ugly hack as I use apple script to send the ctrl b keystrokes to my terminal. Although it works, there's significant latency such that I have to wait ~0.5s after pressing space before I can press any tmux hotkey for them to register correctly.
I feel like there has to be a better way to do this. Is there a way to (1) send key combinations back to the terminal from zsh natively or (2) somehow buffer any subsequent keypresses after space to wait until the osascript command has finished executing?

Well... a simple solution is to use tmux from the command line and directly create the commands I want:
function tmux_vsplit() {
tmux split-window -v
}
function tmux_hsplit() {
tmux split-window -h
}
function zvm_after_lazy_keybindings() {
zvm_define_widget tmux_vsplit
zvm_define_widget tmux_hsplit
zvm_bindkey vicmd ' v' tmux_vsplit
zvm_bindkey vicmd ' h' tmux_hsplit
}
This doesn't give the freedom of being able to properly enter the prefix combo, but it gets the job done!

Related

How can I write a text file, and then read from it in the same Bash script?

I have a hotkey in Vim that takes me into command mode and calls a Bash script. The Bash script attempts the following steps:
Select text between two tokens
Send selected text block to a .py file
Load the .py file into IPython
The script works if I type everything in by hand, but if I run the script, the text block is not saved as a file until after the script is finished, causing an error when IPython tries to load the file later in the script.
Here are the steps I've tried:
In the event my file is being held in the buffer, I tried syncing and flushing
In case the file needed more time to be written, I tried sleep and wait
I also tried asynchronous shell commands to see if the Bash script was getting priority over Vim writing the file.
#!/bin/bash
# Text to be written has been selected in Vim
tmux send-keys ':w jtemp.py'
tmux send-keys 'Enter'
# Load code selection in IPython
tmux select-pane -t 1
tmux send-keys '%load jtemp.py'
tmux send-keys 'Enter'
tmux send-keys 'Enter'
How can I get the file to be saved to disk while the Bash script is still in process?
The problem is most likely (I don't know this for certain however) that Vim is blocking and not processing the key presses until the script finishes. I don't know if it is possible to make Vim run the script in the background, but you could if you bind the hotkey in tmux instead using run-shell -b, something like:
bind F1 run -b "bash /path/to/my/script"
In fact, if doing it in tmux you probably won't need the script to run in the background, because the two affected applications (Vim and IPython) will not block and it doesn't matter if tmux does. So you could just leave the -b out.
You will still almost certainly need a sleep of a second or half a second ("sleep 1" or "sleep 0.5", experiment) between the Enter and the select-pane or there will be a race between Vim writing the file and IPython reading it.
I am not sure of the overhead of calling tmux in a shell script, but I would recommend you script your workflow process without it—you can use sed to parse for text between two tokens in a file, pipe that to a new .py file, and then load that .py file into IPython.

bind source-file in tmux on startup from bash

I have a .tmux.conf file which binds a set of further tmux instructions when I press the correct keys (in this case, Ctrl + b followed by 'k'):
bind k source-file ~/.tmux/myfile
When I go into my tmux terminal and press Ctrl + b followed by k, it works fine, and the script in myfile runs as expected. I'm now trying to launch this all using a command in my .bashrc file by sending the keys to the tmux session once it has launched:
launch_things() {
tmux new-session -d -s mysession
tmux send-keys -t mysession C-b k
tmux a -t mysession
}
However, this doesn't work. It's starting the tmux session, apparently benting the keys to it, then attaching to the session. I'm not sure if it's because I've done something wrong with the sending of the keys, or if it's not possible to send combinations of keys like this. Note that I can see the letter k on the screen when I attach, so the send-keys functionality is doing something.
Can anyone either tell me what I'm doing wrong, or suggest another way to launch my bound source-file (~/.tmux/myfile) from my bash script please?
P.S. I'm running Ubuntu 16.04 if that makes a difference.
It seems that send-keys when sending strings does not try to interpret them for the prefix character. There is the command send-prefix but that behaves in the same way.
So the simple answer is to do tmux source-file ~/.tmux/myfile or perhaps the longer
tmux attach-session -t mysession\; source-file ~/.tmux/myfile\; detach-client

How to enter keyboard shortcuts in bash script

Is it possible to enter keyboard shortcuts into a bash script? For example, I have tried typing in ^d to substitute for control + d (logout) without success.
ubuntu#vfleet:~$ ^d
-bash: :s^d: substitution failed
ubuntu#vfleet:~$ ^D
-bash: :s^D: substitution failed
I am using screen to run a django development server in the background. One of the commands to run a particular screen as a daemon is control + a + d. The goal is to be able to enter in control + a + d into a bash script. For example:
python manage.py runserver
^a + d
Is this possible?
Edit:
A valid method of avoiding the keyboard shortcut in screen is linked by Eric Renouf in the comments below. screen -d -m sh -c "python manage.py runserver" will start a development server as a daemon. This is a great solution for my particular problem, but it would still be nice to have a solution for the original question at hand.
xdotool package is the solution here.
xdotool key ctrl+d
Full reference
You probably should just start the command without attaching to the screen session in the first place, like
screen -d -m python manage.py runserver
but if you can't do that for some reason, you could detach from a screen session you're currently in by doing:
screen -S "$STY" -X detach
screen saves its current session info in STY, so we'll use that to make sure we're interacting with the correct session (in case there are many). Then we'll use -X to send a command to that session, in this case our command will be detach which will detach all the attached sessions, including the one used to execute that command
So while this doesn't actually send key strokes, it does highlight that there is often another command that you can send to accomplish your goals. Here detach takes the place of ctrl+a+d. Sending quit or running exit could often replace ctrl+d.
Another work-around would be to use expect which you could then use to send the strings containing control characters or hex values of them.

How to clear bell state from all tmux windows

I help maintain a large number of Unix-like servers, and so keep a script called tmux-rebuild that I use to rebuild all the tmux sessions and windows with SSH links to each server.
I have tmux configured to show the window's name in red with an exclamation mark in its status bar when a terminal bell character is printed in that window. This is very handy for programs like irssi alerting me to when I have messages in another window.
I also have my $PS1 set up on every server to print a terminal bell at the end of every prompt. This is useful because if I run a long job in one window and switch to another, I can immediately see when it's finished because when my prompt is written to the screen after the job is done, tmux makes the window name come up in red with an exclamation mark. This is great for my workflow.
However it causes a slight problem with the rebuild script mentioned above, because when I start up tmux after running it, every window in every session is flagged in red, due to the first prompt being printed to the screen. This makes the feature useless until I visit every window, and there are something like 40-50 of them.
Is there something I can add to my script that will clear all alerts from sessions and windows after they are created? I don't mind using a kludge if necessary.
From the tmux man page, specifically the last sentence here:
kill-session [-aC] [-t target-session]
Destroy the given session, closing any windows linked to it
and no other sessions, and detaching all clients attached
to it. If -a is given, all sessions but the specified one is
killed. The -C flag clears alerts (bell, activity, or
silence) in all windows linked to the session.
So, simply:
tmux kill-session -C
Figured out an acceptable workaround; I redefined the next/previous bindings to allow repeats:
# Allow repeats for next/prev window
bind-key -r n next-window
bind-key -r p previous-window
This allows me to quickly sweep up the alerts for all windows in a session by pressing my prefix key and tapping "n" until they're all clear, and I'm back in my original window.
With tmux 1.6 (and later), list-windows can generate customizable output, so it is fairly simple to read the output lines and make a loop that runs select-window for each window.
Add list-session (to loop over all sessions, optionally), and display-message (to parse session specifiers, and to record the current/“last” windows so they can be properly restored), and you might end up with something like this:
#!/bin/sh
# usage: tmux-select-each [session [...]]
#
# Select every window in specified session(s). If no sessions are
# specified, process all windows in all sessions.
#
# This can be handy for clearing the activity flags of windows in
# freshly spawned sessions.
if test $# -gt 0; then
for session; do
tmux display-message -p -t "$session:" '#S'
done
else
tmux list-sessions -F '#{session_name}'
fi |
while read -r session; do
active_window=$(tmux display-message -p -t "$session:" '#S:#I')
last_window=$(tmux display-message -p -t "$session:"\! '#S:#I' 2>/dev/null)
tmux list-windows -t "$session" -F '#{session_name}:#{window_index}' |
while read -r window; do
if test "$window" = "$active_window" ||
test "$window" = "$last_window"; then
continue
fi
tmux select-window -t "$window"
done
if [ -n "$last_window" ]; then
tmux select-window -t "$last_window"
fi
tmux select-window -t "$active_window"
done

OSX - How to auto Close Terminal window after the "exit" command executed.

When I'm done with Terminal, I want to exit it. Right now, I have three options:
killall Terminal. It will end the process, but rather abruptly. I don't think this is the best idea.
Call exit. I've changed the settings so exit closes Terminal. The app still appears open in the Dock though, and doesn't do what I want it to do.
Right click>Quit. However, this isn't a Terminal command to exit it.
So now, what is the way I should exit and close Terminal? I've heard something about osascript but I'm not too sure. I want to exit and close, so that Terminal is no longer open, both as a window and as a process.
in Terminal.app
Preferences > Profiles > (Select a Profile) > Shell.
on 'When the shell exits' chosen 'Close the window'
How about the good old Command-Q?
Actually, you should set a config on your Terminal, when your Terminal is up press ⌘+, then you will see below screen:
Then press shell tab and you will see below screen:
Now select Close if the shell exited cleanly for When the shell exits.
By the above config each time with exit command the Terminal will close but won't quit.
In the Terminal app, Preference >> Profiles tab.
Select the Shell tab on the right.
You can choose Never Ask before closing to suppress the warning.
You could use AppleScript through the osascript command:
osascript -e 'tell application "Terminal" to quit'
In a terminal window, you can type:
kill -9 $(ps -p $PPID -o ppid=)
This will kill the Terminal application process, which is the parent of the parent of the current process, as seen by the kill command.
To close a Terminal window from within a running script, you need to go up one more level in the process hierarchy like this:
kill -9 $(ps -p $(ps -p $PPID -o ppid=) -o ppid=)
I 've been using ctrl + d. It throws you out into the destination where You've started the sqlite3 command in the first place.
osascript -e "tell application \"System Events\" to keystroke \"w\" using command down"
This simulates a CMD + w keypress.
If you want Terminal to quit completely you can use:
osascript -e "tell application \"System Events\" to keystroke \"q\" using command down"
This doesn't give any errors and makes the Terminal stop cleanly.
You can also use this convoluted command, which does not trigger a warning about terminating its own process:
osascript -e "do shell script \"osascript -e \\\"tell application \\\\\\\"Terminal\\\\\\\" to quit\\\" &> /dev/null &\""; exit
This command is quite long, so you could define an alias (such as quit) in your bash profile:
alias quit='osascript -e "do shell script \"osascript -e \\\"tell application \\\\\\\"Terminal\\\\\\\" to quit\\\" &> /dev/null &\""; exit'
This would allow you to simply type quit into terminal without having to fiddle with any other settings.
Use the osascript command in your code as icktoofay mentioned: osascript -e 'tell application "Terminal" to quit'
Then, open Terminal preferences, go to Settings > Shell, and set "Prompt before closing:" to "Never." Terminal should now quit completely (not remain open in your dock) and ignore the prompt before quitting. If you have only one Terminal window open and the osascript command is your last line of code, it should wait for whatever command you ran before to finish.
This would not be ideal if you are running scripts in the same window or other windows in the background (for instance, you may run a command in the background and continue using the current window for other commands if the first command is followed by an ampersand); be careful!
If you wrap the osascript code in a shell script file, you can probably call it with whatever pithy file-name you give it---as long as it is in Terminal's search path (run echo $PATH to see where Terminal looks for scripts).
I've been using
quit -n terminal
at the end of my scripts. You have to have the terminal set to never prompt in preferences
So Terminal > Preferences > Settings > Shell
When the shell exits
Close the window
Prompt before closing
Never
Create a script:
cat ~/exit.scpt
like this:
Note: If there is only one window, just quit the application, else simulate command + w to close the tab)
tell application "Terminal"
set WindowNum to get window count
if WindowNum = 1 then
quit
else
tell application "System Events" to keystroke "w" using command down
end if
end tell
Then add a alias in your *shrc
just like vi ~/.bashrc or zshrc (anything else?)
add it:
alias exit="osascript ~/exit.scpt"
And source the ~/.bashrc or reopen your terminal.app
This is what I did for a command I just wrote. I wanted to be able to create a "shortcut" to the Backup directory for Apple Configurator that worked on different computers, but since it's relative to the user's home directory, I needed to make it a .command file. Here are the contents:
#!/usr/bin/env bash
open "${HOME}/Library/Application Support/MobileSync/Backup"
(sleep 0.1 ; osascript -e 'tell application "Terminal" to quit') &
I tried several variations of the answers here. No matter what I try, I can always find a use case where the user is prompted to close Terminal.
Since my script is a simple (drutil -drive 2 tray open -- to open a specific DVD drive), the user does not need to see the Terminal window while the script runs.
My solution was to turn the script into an app, which runs the script without displaying a Terminal window. The added benefit is that any terminal windows that are already open stay open, and if none are open, then Terminal doesn't stay resident after the script ends. It doesn't seem to launch Terminal at all to run the bash script.
I followed these instructions to turn my script into an app: https://superuser.com/a/1354541/162011
If this is a Mac you type 'exit' then press return.

Resources