Hide text segments in vim for use in external commands - vim

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:.*//

Related

Get a parsable string with carriage returns

I sometimes use the string() function in order to generate parsable strings.
For example, :echo string("hello world") shows:
'hello world'
But if I add real carriage returns into the string, the result becomes unparsable, simply because
the carriage returns are not transformed.
For example, :echo string("hello\nworld") shows:
'hello
world'
I would have expected:
"hello\nworld"
Is there a way to get a parsable version of any string, without writing a dedicated function?
EDIT
To be more precise, I need "parsable" strings to be used with the :execute command, in order to create customizable mappings. The basic idea is to be able to use the following code, even when there are some CRs in the a:toinsert argument:
function! InsertMapping(lhs, toinsert)
let l:rhs = printf('<c-r>=%s<cr>', string(a:toinsert))
exe 'inoremap' a:lhs l:rhs
endf
" This call is working:
call InsertMapping('<c-r><c-e>', "hello world")
" This one throws an error:
call InsertMapping('<c-r><c-e>', "hello\nworld")
" E492: Not an editor command: world')<cr>
Indeed, the last call to InsertMapping() will try to execute:
inoremap <c-r><c-e> <c-r>='hello
world'<cr>
Buf of course, I need to execute instead:
inoremap <c-r><c-e> <c-r>="hello\nworld"<cr>
Please note that I need to keep <c-r>= because my real use case is more complex, and needs some function calls; so the mapping can't be simplified like this:
inoremap <c-r><c-e> hello<cr>world
Don't know what do you exactly mean "parsable". If you want to use eval() to get the original string value, it is working. You see the real linebreak was printed out because of the echo() function, it expanded the \n as a linebreak.
Give this a try:
let a="hi\nworld"
echo a==eval(string(a))
echo a==eval(string("hi\nworld"))
both will return 1 (true). So it is "parsable".
If I didn't understand the meaning of your "parsable", please make some example.
I finally wrote a dedicated function to stringify a vim string; it's far from perfect, but it will give a "correct" output for all the tested cases. When really nothing at all is special inside the string, it returns a single-quoted string, which may slightly improve performance in some rare cases.
Here is the code:
let s:specials = {
\ "\b":'\b', "\e":'\e', "\f":'\f', "\n":'\n',
\ "\r":'\r', "\t":'\t', "\\":'\\', "\"":'\"',
\}
function! Stringify(source)
let output = ''
let string_is_special = v:false
for i in range(strlen(a:source))
let char = a:source[i]
let ascii = char2nr(char)
let char_is_special = v:false
for [key, str] in items(s:specials)
if char == key
let output .= str
let char_is_special = v:true
let string_is_special = v:true
break
endif
endfor
if !char_is_special
if ascii < 32
let output .= printf('\x%02x', ascii)
let string_is_special = v:true
else
let output .= char
endif
endif
endfor
return printf(string_is_special ? '"%s"' : "'%s'", output)
endf
Here are some quick tests:
let tests = [
\ "simple string",
\ 'back \ slash',
\ "carriage \n return",
\ "utf8 frénçh àccènts",
\ "\x47\x6e\x75",
\ "ctrl-w special key: \<c-w>",
\ ]
for s in tests
echo Stringify(s)
endfor
Here is the tests output:
'simple string'
"back \\ slash"
"carriage \n return"
'utf8 frénçh àccènts'
'Gnu'
"ctrl-w special key: \x17"
In Vim, single quotes and double quotes have different behaviors.
Between double quotes, special characters like \n are interpreted, so:
"hello\nworld"
becomes:
hello
world
and you would have to double the \ to actually get a \n:
"hello\\nworld"
Between single quotes, special characters are not interpreted, so:
'hello\nworld'
becomes:
hello\nworld
All of that is explained under :help expr-" and :help expr-'.

Write ctags support for diff/patch file in vim

I often use this in my project, to see changes :
git diff 5b80e3be314143f 3b34d24a55ab -- include/* src/* | vim -
All the lines starting with the file being changes are written like this:
diff --git a/include/behaviors/mechanical/crystal_behavior.h b/include/behaviors/mechanical/crystal_behavior.h
I would like to be able to navigate easily the files with tagbar, which uses ctags, and obtain for instance the filename, here crystal_behavior.h.
So, I know I can use something like this in a .ctags file:
--langdef=diff
--langmap=diff:.diff
--regex-diff=/^diff --git .*b\/(.*)(\/(.*))+$/\.\. \2/d,file/
Together with this in my .vimrc
let g:tagbar_type_diff = {
\ 'ctagstype' : 'diff',
\ 'kinds' : [
\ 'd:Files',
\ ],
\ 'sort' : 0,
\ }
My problem here is what can I put in here --regex-diff=*, to obtain filename? (I got more or less)
Is it possible to go further and also tag the hunks in each files? This is the most tricky part I cannot get.
Thank you
Bonus question: If I read the diff directly from buffer, tagbar doesn't generate tags. Whereas if I do git diff ...... > patch.diff, and then vim patch.diff, it works.
Universal-ctags(https://ctags.io) has a built-in parser for diff imported from geany:
[jet#localhost tmp]$ cat foo.diff
diff --git a/source.mak b/source.mak
index 2550028..eaa9154 100644
--- a/source.mak
+++ b/source.mak
## -44,6 +44,7 ## PARSER_SOURCES = \
$(PARSER_DIR)/clojure.c \
$(PARSER_DIR)/css.c \
$(PARSER_DIR)/cobol.c \
+ $(PARSER_DIR)/diff.c \
$(PARSER_DIR)/dosbatch.c \
$(PARSER_DIR)/eiffel.c \
$(PARSER_DIR)/erlang.c \
[jet#localhost tmp]$ ~/var/ctags/ctags --fields=+K --sort=no -o - foo.diff
a/source.mak foo.diff /^--- a\/source.mak$/;" modifiedFile
-44,6 +44,7 foo.diff /^## -44,6 +44,7 ## PARSER_SOURCES = \\$/;" hunk modifiedFile:a/source.mak

tmux: how to open file under cursor

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

Add space after colon zencoding-vim for .scss files

I am using the ZenCoding plugin for vim and I want it to expand CSS abbreviations by adding a space after every colon, which is not the current default behaviour for SCSS files.
I checked both Emmet's and zenconding-vim's documentation and I couldn't apply Emmet's custom property:
css.valueSeparator: ": "
to Vim. Which I tried by adding inside my .vimrc file.
let g:user_zen_settings = {
\ 'css' : {
\ 'valueSeparator' : ': '
\ }
\}
I am not sure whether I am missing something in ZenCoding's documentation or I am trying something that can't be done in ZenCoding.
I finally found the answer among zencoding-vim's issues:
https://github.com/mattn/zencoding-vim/issues/94
You must add this to to your .vimrc file:
let g:user_zen_settings = {
\ 'scss' : {
\ 'filters' : 'fc',
\ }
\}
Important! Don't miss the leading backslashes. They are required.

VIM: Check if a file is open in current tab? window? (and activate it)

In vim, you can check if a file is open in the current buffer with bufexists. For a short filename (not full path), you can check if it's open using bufexists(bufname('filename')).
Is there any way to check if a file is open in a tab?
My closest workaround is to do something like:
:tabdo if bufnr(bufname('filename')) in tabpagebuflist(): echo "Yes"
However, that's sort of pythonic pseudocode... I'm not sure how to get that to work in vim. My goal is for an external applescript to check if a file is already open and if so go to a line in that file.
Ideally, I'd like to be able to search through different GUI windows too, but I've gathered (e.g. Open vim tab in new (GUI) window?) that working with different GUI windows is very challenging / impossible in VIM.
My impatience and good documentation got the better of me... here's the solution (greatly aided by Check if current tab is empty in vim and Open vim tab in new (GUI) window?). The source is at https://github.com/keflavich/macvim-skim
function! WhichTab(filename)
" Try to determine whether file is open in any tab.
" Return number of tab it's open in
let buffername = bufname(a:filename)
if buffername == ""
return 0
endif
let buffernumber = bufnr(buffername)
" tabdo will loop through pages and leave you on the last one;
" this is to make sure we don't leave the current page
let currenttab = tabpagenr()
let tab_arr = []
tabdo let tab_arr += tabpagebuflist()
" return to current page
exec "tabnext ".currenttab
" Start checking tab numbers for matches
let i = 0
for tnum in tab_arr
let i += 1
echo "tnum: ".tnum." buff: ".buffernumber." i: ".i
if tnum == buffernumber
return i
endif
endfor
endfunction
function! WhichWindow(filename)
" Try to determine whether the file is open in any GVIM *window*
let serverlist = split(serverlist(),"\n")
"let currentserver = ????
for server in serverlist
let remotetabnum = remote_expr(server,
\"WhichTab('".a:filename."')")
if remotetabnum != 0
return server
endif
endfor
endfunction
then use like so:
exec "tabnext ".WhichTab('my_filename')
echo remote_foreground( WhichWindow('my_filename') )
or, from the command line, here's a script to go to a particular line of a file using WhichTab:
#!/bin/bash
file="$1"
line="$2"
for server in `mvim --serverlist`
do
foundfile=`mvim --servername $server --remote-expr "WhichTab('$file')"`
if [[ $foundfile > 0 ]]
then
mvim --servername $server --remote-expr "foreground()"
mvim --servername $server --remote-send ":exec \"tabnext $foundfile\" <CR>"
mvim --servername $server --remote-send ":$line <CR>"
fi
done
I'd reply to keflavich, but I can't yet...
I was working on a similar problem where I wanted to mimic the behavior of gvim --remote-tab-silent when opening files inside of gvim. I found this WhichTab script of yours, but ran into problems when there is more than one window open in any given tab. If you split windows inside of tabs, then you will have more than one buffer returned by tabpagebuflist(), so your method of using the buffer number's position in the List doesn't work. Here's my solution that accounts for that possibility.
" Note: returns a list of tabnos where the buf is found or 0 for none.
" tabnos start at 1, so 0 is always invalid
function! WhichTabNo(bufNo)
let tabNos = []
for tabNo in range(1, tabpagenr("$"))
for bufInTab in tabpagebuflist(tabNo)
if (bufInTab == a:bufNo)
call add(tabNos, tabNo)
endif
endfor
endfor
let numBufsFound = len(tabNos)
return (numBufsFound == 0) ? 0 : tabNos
endfunction
I think I can just return tabNos which will be an empty list that gets evaluated as a scalar 0, but I just learned vimscript and am not that comfortable with the particulars of its dynamic typing behavior yet, so I'm leaving it like that for now.

Resources