i am a vim user and got used to the gf command, which opens the file under the cursor.
Now i wanted to ask, if there is something like that for tmux.
I can navigate through a tmux-pane and it happens often that there is a file-path under the cursor. Now i like to have the possibility to open that file under the cursor with vim.
A: in the current window
B: in another window which includes and opened vim
Maybe there is a possibility to run a sh-script in that navigation-mode when invoking a special key-combination? that would make it possible to write my own scripts like i got used to in vim with vimscript.
I am already using some vi-copy modes in mux .tmux.conf
# VIM
# ===================================================================
# Vimlike copy mode.
unbind [
bind Escape copy-mode
unbind p
bind p paste-buffer
bind -t vi-copy 'v' begin-selection
bind -t vi-copy 'y' copy-selection
# Enable vi keys.
setw -g mode-keys vi
# https://coderwall.com/p/4b0d0a/how-to-copy-and-paste-with-tmux-on-ubuntu
bind -t vi-copy y copy-pipe "xclip -sel clip -i"
I've been searching for an answer for years and ended up making a tmux plugin: https://github.com/artemave/tmux_super_fingers. It still baffles me how such in integral part of terminal based workflow is not a solved problem (well, it is now).
To achieve what you want, you need to use the stdin in your command line (xargs can do that) and tell tmux, in a new-window, to open the data with the arguments from the copy buffer:
bind -t vi-copy y copy-pipe "xargs -I{} tmux new-window 'vim {}'"
This needs more tuning (getting the right session, the right command, use $EDITOR instead of vim etc.
It is quite dangerous: Think copying /foo/bar/my;rm -rf /.
Also, as-is, this will only work for paths relative to tmux' working directory.
There's a mod for tmux allowing to bind an action of any complexity in 'mode': http://ershov.github.io/tmux/
There's an example of how to mark the word under cursor using that patch:
proc is_word_char {c} {
print [scan $c %c]
return [expr {$c > " " && $c != "\x7f"}]
}
proc mark-current-word {} {
clear-selection
set l [copy-mode-screenline]
set x [copy-mode-get-cx]
if {![is_word_char [string range $l $x $x]]} return
incr x
while {[is_word_char [string range $l $x $x]]} {
cursor-right
incr x
}
incr x -2
begin-selection
while {[is_word_char [string range $l $x $x]]} {
cursor-left
if {$x < 1} return
incr x -1
}
}
# Open selection in a vim mini-window (no shell and files)
bind-key -t vi-copy y tcl {
split-window -c [f #{pane_current_path}] -l 5 "
echo -n [shell-quote [copy-mode-selection]] | vim -R -"
}
Hence, to open the current file in vim:
mark-current-word
split-window -c [f #{pane_current_path}] -l 5 "vim -R [shell-quote [copy-mode-selection]]"
So i got it running with the following binding:
bind -t vi-copy y copy-pipe "xargs -I{} tmux send-keys -t 1 ';edit {}' Enter && tmux select-pane -t 1"
notes
i changed vim command : to ;
i have a open vim in pane 1
Related
I use screen and vim a lot. Therefore, when I go to open a file that I already have open in another vim session, in another screen window, I hate playing "where's waldo" and trying to figure out just which of my 22 screen windows that file happens to be opened in.
Therefore, I wrote the following .vimrc functions, that have worked well for me over the years in both redhat, debian, ubuntu, and centos. However, it isn't working in cygwin, because "lsof" is not found. From time to time I have had to slightly modify the functions, just to get the paths to work, but in general, these little guys have done well for me. When I go to open a file that is already opened in another window in vim, vim will actually tell me WHICH window (by number) it is opened in, so that I can go there, and close it, or edit it from there.
Could someone please help me adjust this so that it would work in cygwin on Windows10?
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" for screen use
" opened in another screen? lets not play where's waldo anymore!
" this will politely tell me which screen's window it is open in so that I may
" go close it if I want, but still provide me the normal options
"
augroup NoSimultaneousEdits
autocmd!
autocmd SwapExists * :call PrintScreenWindow()
augroup END
function! PrintScreenWindow ()
let fname = expand("%:p")
" fix fname here, remove the path and leave only the filename/basename
let fname = fnamemodify(fname, ':t')
" I just added the 'fpath', and 'all' variables,
" The below my_command USED to use fname, but I found that if you have the
" file open somewhere else (in a different screen window) AND also, have the
" same filename, with a different path open, in a different window, it
" causes an error, I.E.:
" /root/abc/test.txt -- open in window 0
" /root/abc/def/test.text -- open in window 1
" now, in window 2, try to open /root/abc/test.txt, the below my_command
" USED to have the variable 'fname', where it now has the variable 'all'
" and this caused an error.
" Adding this 'fpath', and 'all', fixes this issue.
let fpath = expand("%:p:h")
let all = fpath . "/." . fname
" you might have to fix your path to lsof
let my_command = "lsof | grep '" . fname . ".swp' | grep " . $USER . " | sed -n 's/^vim\\? \\+\\([0-9]\\+\\).*$/\\1/p' "
" let my_command = "lsof | grep '" . all . ".swp' | /bin/grep " . $USER . " | sed -n 's/^vim\\? \\+\\([0-9]\\+\\).*$/\\1/p' "
let result = substitute(system(my_command), '\n','','')
if result
let my_cmd2 = "cat /proc/" . result . "/environ | xargs -0 echo | sed -n 's/.*WINDOW=\\([0-9]*\\).*/\\1/p' "
let res2 = substitute(system(my_cmd2), '\n','','')
if res2 || res2 == '0'
echo 'This file is already opened in window: ' . res2
else
echo "command failed: " . my_cmd2
endif
else
echo my_command . " : cmd failed"
endif
endfunction
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
Okay, for CYGWIN, you do NEED to install "busybox"
Then, in my .vimrc file, change:
let my_command = "lsof | grep '" . fname . ".swp' | grep " . $USER . " | sed -n 's/^vim\\? \\+\\([0-9]\\+\\).*$/\\1/p' "
to:
let my_command = "busybox lsof | grep '" . fname . ".swp' | cut -f1 "
As there are no users listed, and the PID comes first, not after the process name.
Secondly, change:
let my_cmd2 = "cat /proc/" . result . "/environ | xargs -0 echo | sed -n 's/.*WINDOW=\\([0-9]*\\).*/\\1/p' "
to:
let my_cmd2 = "cat /proc/" . result . "/environ | xargs -0 echo | busybox sed -n 's/.*WINDOW=\\([0-9]*\\).*/\\1/p' "
When I open vim inside of a TMUX pane, The pane is filled with codes I don't recognise. If I just run vim, I get this:
^[[38;2;165;42;42m 1
^[[38;2;0;0;255m~
If I open a file with vim, I get something like this (top pane):
Pretty new to both vim and TMUX. How can I solve this?
It seems that Vim is sending control sequences to your terminal which the latter doesn't understand.
More specifically, the sequences you mentioned in the OP:
^[[38;2;165;42;42m
^[[38;2;0;0;255m
look like they're encoding foreground true colors for the text.
You can find their syntax here:
CSI Pm m
Where CSI stands for “Control Sequence Introducer” and is produced by the keys ESC [, and Pm stands for:
A multiple numeric parameter composed of any number of single numeric parameters, separated by ; character(s).
If you scroll down the page, you should find a description of a more detailed syntax:
CSI Pm m Character Attributes (SGR).
...
This variation on ISO-8613-6 is supported for compatibility with KDE konsole:
Pm = 3 8 ; 2 ; Pr; Pg; Pb
Set foreground color to the closest match in xterm's palette for
the given RGB Pr/Pg/Pb.
Pm = 4 8 ; 2 ; Pr; Pg; Pb
Set background color to the closest match in xterm's palette for
the given RGB Pr/Pg/Pb.*
Applied to your first sequence, you can break it down like so:
┌ CSI
│ ┌ Pm
├─┐├────────────┐
^[[38;2;165;42;42m
├─┘ ├┘ ├┘
│ │ └ Pb = amount of blue
│ └ Pg = amount of green
└ Pr = amount of red
If the terminal doesn't understand this sequence, I can see 3 explanations:
the terminal doesn't support true colors
tmux is not properly configured to support true colors
Vim is not properly configured to support true colors
To test whether 1. is the issue, you can write this bash function in your ~/.bashrc:
truecolor() {
local i r g b
for ((i = 0; i <= 79; i++)); do
b=$((i*255/79))
g=$((2*b))
r=$((255-b))
if [[ $g -gt 255 ]]; then
g=$((2*255 - g))
fi
printf -- '\e[48;2;%d;%d;%dm \e[0m' "$r" "$g" "$b"
done
printf -- '\n'
}
Then execute $ truecolor in your shell, outside tmux. If you get some kind of rainbow, your terminal supports true colors (at least partially).
If you see some cells which are not colored, and others randomly colored, your terminal doesn't support true colors.
Alternatively, you can try the sequences manually:
$ printf '\e[38;2;%d;%d;%dm this text should be colored \e[0m' 165 42 42
$ printf '\e[38;2;%d;%d;%dm this text should be colored \e[0m' 0 0 255
If $ truecolor doesn't produce a rainbow, or if the $ printf commands don't change the foreground color (not the background color) of the text, you'll have to either:
disable 'termguicolors' in your ~/.vimrc; i.e. remove set termguicolors (or make it execute set notermguicolors)
try and upgrade your terminal
find another terminal which supports true colors
To test whether 2. is the issue, inside tmux, you can execute this shell command:
$ tmux info | grep Tc
If the output contains [missing]:
203: Tc: [missing]
^^^^^^^^^
it means that tmux is not configured to support true colors.
In this case, you have to include something like this in ~/.tmux.conf:
set -as terminal-overrides ',*-256color:Tc'
││ ├────────────────┘ ├────────┘ ├┘
││ │ │ └ tell tmux that the terminal suppors true colors
││ │ └ configure the option only if `$TERM` ends with the string `-256color`
││ └ the option to configure is `terminal-overrides` (see `$ man tmux`)
│└ the next option is a server option
└ append the value to the tmux option instead of overwriting it
Then restart tmux, and execute $ tmux info | grep Tc. This time the output should contain true:
203: Tc: (flag) true
^^^^
If it doesn't, have a look at the output of $TERM outside tmux:
$ echo $TERM
The output should be matched by whatever pattern you write before :Tc.
In the previous example, I used the pattern *-256color which will match any terminal whose $TERM ends with the string -256color. If it doesn't match your $TERM, you can try another pattern, or simply write * to describe any type of terminal:
set -as terminal-overrides ',*:Tc'
To test whether 3. is the issue, you can write these commands in your ~/.vimrc:
set termguicolors
let &t_8f = "\<Esc>[38:2:%lu:%lu:%lum"
let &t_8b = "\<Esc>[48:2:%lu:%lu:%lum"
Or:
set termguicolors
let &t_8f = "\<Esc>[38;2;%lu;%lu;%lum"
let &t_8b = "\<Esc>[48;2;%lu;%lu;%lum"
The only difference between the 2 versions is the delimiter between the parameters of the sequences. A colon in the first version, a semicolon in the second one. See :h xterm-true-color for more info.
You can check the current values of these 3 options by executing successively:
:echo &tgc
:echo &t_8f
:echo &t_8b
They should output:
1
^[[38:2:%lu:%lu:%lum
^[[48:2:%lu:%lu:%lum
Or:
1
^[[38;2;%lu;%lu;%lum
^[[48;2;%lu;%lu;%lum
I am trying to work around a situation where I need to login to a multitude
of servers without being able to utilize ssh keys. As a result I am
formulating a "that is a bad practice"-expect script:
#!/usr/bin/expect
set arg1 [lindex $argv 0]
spawn ssh $arg1 -l user "hostname; env x='() { :;}; echo vulnerable' bash -c \"echo this is a test\"; echo"
expect " password:"
send "my_supersecret_password\n"
interact
Running it works fine:
$ ./ssh.expect server
spawn ssh server -l user hostname; env x='() { :;}; echo vulnerable' bash -c "echo this is a test"; echo
user#server's password:
server
this is a test
$
But I need a better formatted list when running on more than one system, so I attempt to let perl reformat the data:
$ ./ssh.expect server | perl -e '$dump = <>; $dump = <>; chomp($line1 = <>); chomp($line2 = <>); $dump = <>; print "$line1:$line2\n";'
:this is a test
The server name is printed as if it ends with a \r. I don't think that it should. Do you agree? How can I get the system to not return to column 0 after printing the server name?
I can verify that both variables contain data by adding a newline to my print:
$ ./ssh.expect server | perl -e '$dump = <>; $dump = <>; chomp($line1 = <>); chomp($line2 = <>); $dump = <>; print "$line1\n:$line2\n";'
server
:this is a test
EDIT:
As commented, the following works.
./ssh.expect server | tr -d '\r' | perl -e '$dump = <>; $dump = <>; chomp($line1 = <>); chomp($line2 = <>); $dump = <>; print "$line1:$line2\n";'
server:this is a test
Shouldn't chomp make the tr redundant?
Expect uses a pseudo-TTY to communicate with the command that it spawns, which in this case is the ssh process. TTYs and PTYs by default will translate \n to \r\n (LF to CRLF) in output text. So the output of your expect script would contain CRLF sequences unless you took the effort to remove the CR's. You can see this by running expect interactively:
$ expect -c 'spawn echo foo; interact' | od -a
0000000 s p a w n sp e c h o sp f o o cr nl
0000020 f o o cr nl
0000025 ^^--^^--note
TTY LF->CRLF conversion is controlled by the TTY "onlcr" flag. I played around with turning that flag off in the expect script but wasn't successful.
Perl's chomp command removes sequences of the perl input record separator (the $/ variable), which will just be \n on Unix systems. In other words, \r isn't special to chomp on unix by default. You could alter $/ in your perl script to make chomp remove the carriage returns, if you like. Or you could pipe the output of your expect script through tr, as discussed.
\r is used by terminals; the Enter key on your keyboard actually sends \r and the Linux kernel translates it to \n; \r\n is the line ending for output to the terminal and the Linux kernel translates \n to it on output. It's possible to disable those translations, putting the terminal into 'raw mode', in which the program can talk directly to the terminal (see http://www.linusakesson.net/programming/tty/). This is needed for full-screen programs, such as vi, nethack, etc.
My guess is that ssh is putting the terminal into raw mode on the client (your end) so the kernel on the server end can do the \n -> \r\n translation on output. This is what allows you to run vi under ssh, but it means that when you run ssh under a program need to pretend to be a terminal, which means sending \r as EOL on output and reading \r\n as EOL on input.
I am trying to create some sort of an ncurses-like menu appearence in vim.
Each line should have two fields: title, command
I read the input from stdin, formatted like: title COMMAND: command
item 1 COMMAND: echo hey
item 2 COMMAND: ls /
item 3 COMMAND: some-other-command
I want to show only the titles, like so:
item 1
item 2
item 3
But then I want to be able to run the command of this line, like so:
:.w !exec $(sed -r 's/COMMAND: (.*)/\1/')
But I haven't been able to hide the "COMMAND: ..." part.
How can I accomplish this?
Is vim not suited for such adventures?
Thank you...
Here is how to accomplish what you originally intended. There are many options but I personally use the Unite plugin.
Install Unite
Add the following in your vimrc too see possibilities:
let g:unite_source_menu_menus = {}
let g:unite_source_menu_menus.filters = {
\'description' : 'Text filters',
\'command_candidates' : [
\ ["Remove empty lines" , 'v/./d'],
\ ["Remove empty lines [v]" , "'<,'>v/./d"],
\ ['Condense empty lines' , '%s/\n\{3,}/\r\r/e'],
\ ['Remove trailing white space' , '%s/\s\+$//' ],
\ ['',''],
\]}
let g:unite_source_menu_menus.git = {
\ 'description' : 'Git commands (Fugitive)',
\ 'command_candidates' : [
\ ['git status (Fugitive) ' , 'Gstatus'],
\ ['git diff (Fugitive) ' , 'Gdiff'],
\ ['git commit (Fugitive) ' , 'Gcommit'],
\ ['git cd (Fugitive) ' , 'Gcd'],
\ ['',''],
\ ['git view file (gitv) ' , 'Gitv!'],
\ ['git view all (gitv) ' , 'Gitv'],
\]}
let g:unite_source_menu_menus.myhelp = {
\'description' : 'Interesting help topics',
\'command_candidates' : [
\ ['Executing shell commands in a window', 'h shell-window'],
\ ['File Searching and **', 'h starstar'],
\ ['Local directory .vimrc', "h 'exrc'"]
\]}
noremap <silent> sm :<C-u>Unite -no-start-insert -quick-match -buffer-name=menu menu<CR>
You can launch menu with sm. You can change menu options and you can execute, edit, bookmark and do other tasks with commands. There is an option to filter items and use fuzzy search engine.
Unite is unbelievable awesome plugin with many other benefits.
Another option is to use Forms plugin.
As for custom solution, this is a fast idea:
Given the file awesome.menu
item 11 COMMAND: echo 'hey'
item 2 COMMAND: !ls /
item 3 COMMAND: version
you can use the following function.
fu! Menu()
let g:menu_bg = synIDattr(hlID('Normal'), 'bg#')
let g:menu_fg = synIDattr(hlID('Normal'), 'fg#')
let g:menu_s = 1
exe 'highlight MyMenu guifg=' g:menu_fg
match MyMenu /COMMAND:.*/
nn <buffer> <space> :let g:menu_s = !g:menu_s<cr>:exe 'highlight MyMenu guifg=' . '<c-r>=g:menu_s ? g:menu_fg : g:menu_bg<cr>'<cr>
nn <buffer> <cr> :exe substitute(getline('.'),'.*COMMAND: ','','g')<cr>
norm <space>
echo 'Menu Loaded'
endf
au BufCreate *.menu call Menu()
If you add above code in your vimrc and then load awesome.menu, your command text will be hidden and you can use "space" to toggle its visibility and "enter" to execute it.
first thing you need to take care is, :exec doesn't support [range] , that is, you cannot :% exec !whatever
If I understood you right, you want to for each line in your example, pick part of the line, the text after COMMAND:, as external shell command, and execute it.
You can try this command:
exec '!'.substitute(getline('.'),'.*COMMAND: ','','g')
To apply it on all your lines, you can consider to use macro, it is pretty easy. Don't put sed in, it doesn't help much.
If you want to first execute the cmd, and then modify the line, remove COMMAND:.. part, you can chain a :s after the exec ...:
exec '!'.substitute(....)|s/COMMAND:.*//
I want to attach a keybinding to call a script in screen. I have tried "bind ^k /path/to/script" in my .screenrc, reloaded screen and tried C-a-k and C-k and nothing is executed.
The script in question:
#!/bin/bash
INDEXFILE="$HOME/bin/screen/themes/theme_index"
# if this is the first time then set
# the index to 0
if [[ ! -e $INDEXFILE ]]
then
echo 0 > $INDEXFILE
fi
THEMENO=`cat $INDEXFILE`
THEMEMAX=5
if [[ $THEMENO -eq $THEMEMAX ]]
then
THEMENO=0
else
THEMENO=`expr $THEMENO + 1`
fi
echo $THEMENO > $INDEXFILE
THEMEFILE=$HOME/bin/screen/themes/theme${THEMENO}
This references to a themeindex file which will contain a number 0-5. As well as "theme files" which look like this:
#!/bin/bash
# yellow
SESSION=$1
screen -S $SESSION -X caption always "%{= KW}%-w%{= Yk}%n %t%{-}%+w %-="
screen -S $SESSION -X hardstatus alwayslastline "%{= kW} %-= %{= kY}Session: %u%{= kW}%5\` | %{= kY}Host:%{= kW} %H | %{= kY} Uptime:%{= kW} %4\` | %{= kY} MEM:%{= kW} %2\`MB |%{= kY} SW: %{= kW}%3\`MB | %{= kY} DATE:%{= kW} %m/%d/%y %C %A"
Essentially allowing me to change the color of the Gnu Screen theme. Different sessions would be a different color to be more immediately identified visually. The script works on it's own if called directly but i would really like it attached to a key binding.
Instead of using bind ^k /path/to/script you should use bind ^k exec /path/to/script.