vim: execute keystroke in function without triggering function - vim

I am trying to write a vim function that conditionally changes the behavior of the enter key. I want the enter key to sometimes indent, other times behave "normally". By normally I mean that if a series of cases doesn't apply, act like the function/mapping doesn't exist. The trouble I'm running into is that I'm using <CR> as my trigger to invoke the function, and thus I'm not sure how to just say "oh, none of these cases apply, execute a <CR> as if this mapping was never defined."
As an example, consider this in my .vimrc, which indents the line if it starts with an a, otherwise triggers a carriage return. (My vimscript is very novice, so this function might not be correct, but I think the idea remains...)
function! SpecialEnter()
let line=getline(".")
if line =~ '\va*'
" at least one space then a valid indentation
normal! >>
else
" just do the regular thing
" echo "in else"
call feedkeys("\<CR>")
endif
endfunction
inoremap <CR> <Esc>:call SpecialEnter()<CR>
This is somewhat simplified from what I'm actually trying to do, but the concept is the same. I'm looking for a way to say "none of my if statements applied, act like this mapping doesn't exist". Is there a way to do this?

You need to give your mapping the <expr> flag. With this, the right hand side of your mapping is evaluated as an expression.
Here is an example taken from my config where I return different prompts for different commands:
cnoremap <expr> <CR> CCR()
" make list-like commands more intuitive
function! CCR()
let cmdline = getcmdline()
command! -bar Z silent set more|delcommand Z
if cmdline =~ '\v\C^(ls|files|buffers)'
" like :ls but prompts for a buffer command
return "\<CR>:b"
elseif cmdline =~ '\v\C/(#|nu|num|numb|numbe|number)$'
" like :g//# but prompts for a command
return "\<CR>:"
elseif cmdline =~ '\v\C^(dli|il)'
" like :dlist or :ilist but prompts for a count for :djump or :ijump
return "\<CR>:" . cmdline[0] . "j " . split(cmdline, " ")[1] . "\<S-Left>\<Left>"
elseif cmdline =~ '\v\C^(cli|lli)'
" like :clist or :llist but prompts for an error/location number
return "\<CR>:sil " . repeat(cmdline[0], 2) . "\<Space>"
elseif cmdline =~ '\C^old'
" like :oldfiles but prompts for an old file to edit
set nomore
return "\<CR>:Z|e #<"
elseif cmdline =~ '\C^changes'
" like :changes but prompts for a change to jump to
set nomore
return "\<CR>:Z|norm! g;\<S-Left>"
elseif cmdline =~ '\C^ju'
" like :jumps but prompts for a position to jump to
set nomore
return "\<CR>:Z|norm! \<C-o>\<S-Left>"
elseif cmdline =~ '\C^marks'
" like :marks but prompts for a mark to jump to
return "\<CR>:norm! `"
elseif cmdline =~ '\C^undol'
" like :undolist but prompts for a change to undo
return "\<CR>:u "
else
return "\<CR>"
endif
endfunction

Related

Run J script as if it were line-by-line entered into the interpreter

Is there a way to run the script as if it were typed into the interpeter? The benefits are that I don't need echos everywhere, the work done is saved as a file, and I can use vim to do the editing. Example:
example.ijs
x =. 1
x + 3
terminal
x =. 1
x + 3
4
If not, I'll write a vimscript that can do the above then share them here. The advantage of a vimscript solution is that I could have commands to run the entire file, the current line, the current selection, everything up to and including the current line, or whatever else is useful.
Related but not a duplicate: How to call J (ijconsole) with a script automatically
It sounds like you are asking for this to be solved for the jconsole interface. I don't have answer for that, but would point out that that functionality is available for both the JHS and jQt interfaces. If you don't mind switching to a different interface then that would be a quick and easy solution.
The easiest way to interactively run a script is to use labs command:
load'labs/lab'
lab'myscript.ijt'
1 of 2 in myscript.ijt
x =. 1
NB. press Ctrl+'.' to advance.
x + 3
4
NB. Run the whole script.
lab 1 _
x =. 1
x + 3
4
More info about labs here
If you want to non-interactively run a script as if typed in the console, you can just feed the script to j (in linux):
j < myscript.ijs
4
Alternatively, to
see the lines on the screen just as though they had been typed from the keyboard
from a script, you can use 0!:1:
0!:1 < 'myscript.ijs'
x =. 1
x + 3
4
Use loadd rather than load to run the script and display the lines and results.
loadd 'example.ijs'
x =. 1
x + 3
4
This is the vimscript solution I mentioned. It's as far as I can tell language agnostic as well.
" Global variable dictates whether new terminals are opened
" horizontally ('h') or vertically ('v').
let g:terminalsplit = 'h'
" Add execution strings for each language you use.
augroup terminalcommands
autocmd!
autocmd Filetype j let g:cmdstr = 'jconsole.cmd'
autocmd Filetype python let g:cmdstr = 'python'
augroup END
" Close all terminals
nnoremap <silent> <leader>p :call CloseTerminal()<cr>
" Run file
nnoremap <leader>h :call Run(g:cmdstr, 'script')<cr>
" Run as if file were entered line-by-line into the interpreter
" Mappings for: Line, selection, file up to line, entire file
nnoremap <leader>j yy:call Run(g:cmdstr, 'interpreter')<cr>
vnoremap <leader>j ygv<esc>:call Run(g:cmdstr, 'interpreter')<cr>
nnoremap <leader>k Vggy<c-O>:call Run(g:cmdstr, 'interpreter')<cr>
nnoremap <leader>l mzggVGy'z:call Run(g:cmdstr, 'interpreter')<cr>
function! Run(cmdstr, mode)
let filepath = expand('%:p') " Copy filepath before switch to terminal
call CloseTerminal()
call OpenTerminal()
echo g:clear . " & " . a:cmdstr
call feedkeys(g:clear . " & " . a:cmdstr) " Begin run command
call RunCode(filepath, a:mode)
call feedkeys("\<c-w>p") " Switch back to file window
endfunction
function! CloseTerminal()
if has('nvim')
let terminals = split(execute('filter/term:/ls'), '\n')
else
let terminals = split(execute('filter/!/ls'), '\n')
endif
for i in range(len(terminals))
silent! exe "bd! " . split(terminals[i], ' ')[0]
endfor
endfunction
function! OpenTerminal()
if g:terminalsplit == 'h'
terminal
elseif g:terminalsplit == 'v'
vertical terminal
else
echo 'g:terminalsplit=' . &g:terminalsplit . '. Must be "h" or "v".'
endif
endfunction
function! RunCode(filepath, mode)
if a:mode == 'script'
call feedkeys(" " . a:filepath . "\<cr>")
elseif a:mode == 'interpreter'
call feedkeys("\<cr>")
call feedkeys("\<c-w>\"\"")
else
echo 'a:mode=' . a:mode . '. Must be "script" or "interpreter".'
endif
endfunction
" Use to clear the terminal window before running the script
if has('unix')
let g:clear = 'clear'
else
let g:clear = 'cls'
endif

How to combine :b and :ls functionality in Vim?

In Vim, is there a way to make :b list the buffer numbers next to open buffers, in a way similar to the :ls command but without having to retype :b afterwards?
This great mapping, popularized by a most valuable member of the community, does exactly what you want:
nnoremap gb :ls<CR>:b
A more generic approach…
Vim uses that non-interactive list to display the result of a number of other useful commands. I've written this simple function to insert the right "stub" each time I press <CR> after one of those commands:
function! CmdCR()
" grab the content of the command line
let cmdline = getcmdline()
if cmdline =~ '\C^ls'
" like :ls but prompts for a buffer command
return "\<CR>:b"
elseif cmdline =~ '/#$'
" like :g//# but prompts for a command
return "\<CR>:"
elseif cmdline =~ '\v\C^(dli|il)'
" like :dlist or :ilist but prompts for a count for :djump or :ijump
return "\<CR>:" . cmdline[0] . "jump " . split(cmdline, " ")[1] . "\<S-Left>\<Left>"
elseif cmdline =~ '\v\C^(cli|lli)'
" like :clist or :llist but prompts for an error/location number
return "\<CR>:silent " . repeat(cmdline[0], 2) . "\<Space>"
elseif cmdline =~ '\C^old'
" like :oldfiles but prompts for an old file to edit
return "\<CR>:edit #<"
else
" default to a regular <CR>
return "\<CR>"
endif
endfunction
cnoremap <expr> <CR> CmdCR()

How to use vim variables in an external filter command in visual mode?

I'm trying to make a code pretty printer filter (e.g. perltidy) accept arbitrary options depending on vim variables. My goal is to pass project specific options to an external command used as a filter (:!) in visual mode.
The following expresses my intention (the last line is problematic):
" set b:perltidy_options based on dirname of the currently edited file
function! SetProjectVars()
if match(expand("%:p:h"), "/project-foo/") >= 0
let b:perltidy_options = "--profile=$HOME/.perltidyrc-foo --quiet"
elseif match(expand("%:p:h"), "/project-bar/") >= 0
let b:perltidy_options = "--profile=$HOME/.perltidyrc-bar --quiet"
else
let b:perltidy_options = "--quiet"
endif
endfunction
" first set the project specific stuff
autocmd BufRead,BufNewFile * call SetProjectVars()
" then use it
vnoremap ,t :execute "!perltidy " . b:perltidy_options<Enter>
However, the last line (vnoremap) is an error in vim, because it expands to:
:'<,'>execute "!perltidy " . b:perltidy_options
and the execute command cannot accept a range.
But I'd like to have this:
:execute "'<,'>!perltidy " . b:perltidy_options
How can I do this?
p.s. My perltidy is configured to act like a unix filter and I use vim 7.3.
You can use <C-\>e and getcmdline() to preserve command-line contents:
vnoremap ,t :<C-\>e'execute '.string(getcmdline()).'."!perltidy " . b:perltidy_options'<CR><CR>
, but in this case I would suggest simpler <C-r>= which purges out the need for :execute:
vnoremap ,t :!perltidy <C-r>=b:perltidy_options<CR><CR>
If you ever want to get rid of a range in command (ex) mode,
CRL-u will do just that.
vnoremap ,t :execute "!perltidy " . b:perltidy_options<Enter>
becomes
vnoremap ,t :<C-u>execute "!perltidy " . b:perltidy_options<CR>
:h c_CTRL-u
Happy vimming,
-Luke

Create a mapping for Vim's command-line that escapes the contents of a register before inserting it

Suppose that I have a document like this, and I want to search for all occurences of the URL:
Vim resources: [http://example.com/search?q=vim][q]
...
[q]: http://example.com/search?q=vim
I don't want to type it out in full, so I'll place my cursor on the first URL, and run "uyi[ to yank it into the 'u' register. Now to search for it, I'd like to just paste the contents of that register into the search field by running:
/\V<c-r>u<CR>
This results in Vim searching for the string 'http:' - because the '/' character terminates the search field.
I can get around the problem by running this instead:
/\V<c-r>=escape(#u, '\/')<CR><CR>
But it's a lot of typing!
How can I create a mapping for Vim's commandline that simplifies this workflow?
My ideal workflow would go something like this:
press /\V to bring up the search prompt, and use very nomagic mode
hit ctrl-x to trigger the custom mapping (ctrl-x is available)
Vim listens for the next key press... (pressing <Esc> would cancel)
pressing 'u' would escape the contents of the 'u' register, and insert on the command line
Try this:
cnoremap <c-x> <c-r>=<SID>PasteEscaped()<cr>
function! s:PasteEscaped()
" show some kind of feedback
echo ":".getcmdline()."..."
" get a character from the user
let char = getchar()
if char == "\<esc>"
return ''
else
let register_content = getreg(nr2char(char))
return escape(register_content, '\/')
endif
endfunction
By the way, something that might be useful to know (if you don't already) is that you can use ? as the delimiter for :s. Which means that you could write a search-and-replace for an url like so:
:s?http://foo.com?http://bar.com?g
I've accepted Andrew Radev's solution, which solved the hard parts. But here's the version that I've added to my vimrc file, which adds a couple of enhancements:
cnoremap <c-x> <c-r>=<SID>PasteEscaped()<cr>
function! s:PasteEscaped()
echo "\\".getcmdline()."\""
let char = getchar()
if char == "\<esc>"
return ''
else
let register_content = getreg(nr2char(char))
let escaped_register = escape(register_content, '\'.getcmdtype())
return substitute(escaped_register, '\n', '\\n', 'g')
endif
endfunction
This should work:
whether you use / or ? (to search forwards, or backwards)
and when the pasted register includes multiple lines
Also, I changed the prompt. While waiting for a register, the prompt switches to \ - which seems like a suitable cue for 'PasteEscaped'. Also, I've appended a ", which mimics Vim's behavior after pressing <c-r> at the command line.
If you've any further suggestions for improvements, please leave a comment.
How about different workflow? For example, creating your own operator to search target text as is:
" https://gist.github.com/1213642
" Requiement: https://github.com/kana/vim-operator-user
map YourFavoriteKeySequence <Plug>(operator-search-target-text)
call operator#user#define('search-target-text', 'OperatorSerachTargetText')
function! OperatorSerachTargetText(motion_wise)
execute 'normal!' '`['.operator#user#visual_command_from_wise_name(a:motion_wise).'`]"xy'
let #/ = '\V' . escape(substitute(#x, '[\r\n]$', '', ''), '\')
normal! n
endfunction
I like #nelstrom's solution and made a small change to support escaping [ and ].
cnoremap <c-x> <c-r>=<SID>PasteEscaped()<cr>
function! s:PasteEscaped()
echo "\\".getcmdline()."\""
let char = getchar()
if char == "\<esc>"
return ''
else
let register_content = getreg(nr2char(char))
let escaped_register = escape(register_content, '\'.getcmdtype())
let escaped_register2 = substitute(escaped_register,'[','\\[','g')
let escaped_register3 = substitute(escaped_register2,']','\\]','g')
return substitute(escaped_register3, '\n', '\\n', 'g')
endif
endfunction

Vim autocommand trigger on opening "nothing"

I want vim to open up the :Explorer when no file is opened or created. Eg. when I call vim without any options.
calling vim newfile.txt should still behave the normal way though.
How would I go about doing this? I can't seem to find the correct autocmd for it.
If you want to do this for vim invocation only, the best way is to use argc():
autocmd VimEnter * :if argc() is 0 | Explore | endif
argc() function returns a number of filenames specified on command-line when vim was invoked unless something modified arguments list, more information at :h argc().
Found the answer myself:
"open to Explorer when no file is opened
function! TabIsEmpty()
" Remember which window we're in at the moment
let initial_win_num = winnr()
let win_count = 0
" Add the length of the file name on to count:
" this will be 0 if there is no file name
windo let win_count += len(expand('%'))
" Go back to the initial window
exe initial_win_num . "wincmd w"
" Check count
if win_count == 0
" Tab page is empty
return 1
else
return 0
endif
endfunction
" Test it like this:
" echo TabIsEmpty()
function! OpenExplorer()
if (TabIsEmpty())
:Explore
end
endfunction
The greatest part of this code was taken from this question.

Resources