How to append text to end of current line using Vim function? - vim

I'd like to append text to the end of a current line in Vim. I'd like to do this within the context of a function.
How can this be done? Do I need to escape/sanitise the text?

You could use the normal command with the execute command:
let text_for_appending = ' # a comment'
execute "normal! A" . text_for_appending
The exclamation mark is included to prevent any key mappings from being expanded. See :help :normal for more details.

With :exe + :normal! you may need to sanatize the text as you feared -- it'll depend on the kind of quotes you use, and on whether you forget to bang :normal and you have insert mode mappings and abbreviations.
With setline('.', getline('.') . text), vim won't try to interpret the text you append. This seems convoluted, but this is the more robust way to proceed -- it can become way more convoluted if you start to escape things with A.

Related

Vim Surround command

I have trouble remembering all the various keywords in vim. However, things like 'surround' from Tim Pope's surround.vim plugin are very useful.
Is it possible to set up a generic text command such that when I execute something like
:surround (.)
it will replace the current selection with
(<current_selection>)
or if I execute
:surround preamble.somethingelse
It will replace the current selection with:
preamble<current_selection>somethingelse
Alternatively you can use a custom "surrounding" which will prompt you for the text:
let g:surround_{char2nr('-')} = "\1start: \1\r\2end: \2"
Now you can visually select what you want to wrap and then press S- to tigger the - surrounding. It will then prompt you for a "start" and "ending" text.
For more help see:
:h surround-customizing
:h char2nr()
:h curly-brace-names
Almost, yes.
:command! -range -nargs=1 Surround normal gv"zc<args><Esc>F.v"zp
With this, you can create a visual selection, then use
:Surround (.)
:Surround preamble.somethingelse
Note that user-defined commands can never start with a lowercase letter, so :surround that you ask for is not possible. Also, this is a quick hack, so it's rather fragile.
However, as per comments, I would urge you to use more standard Vim methods (and well-vetted plugins like surround.vim) before cooking up custom ways to use it.
Here's another way, using surround plugin: define a custom replacement and perform it (then fix the plugin config to what it was before):
command! -range -nargs=1 Surround call ArbitrarySurround(<q-args>)
function! ArbitrarySurround(repl)
let backup = b:surround_45
let b:surround_45 = substitute(a:repl, "\\.", "\r", "")
norm gvS-
let b:surround_45 = backup
endfunction
This is more robust than the previous one, allowing ad-hoc custom replacement pairs, but does require the surround plugin. It can be used with the same syntax as the above one.
In order to leverage the surround.vim plugin, you have to base your customization on what the plugin offers. For visual mode, it's basically this mapping (that by default is mapped to S):
vnoremap <silent> <Plug>VSurround :<C-U>call <SID>opfunc(visualmode(),visualmode() ==# 'V' ? 1 : 0)<CR>
The plugin does not expose any functions (the <SID>opfunc() it invokes is script-local), so you have to invoke the <Plug>-mapping; that can easily be done with :normal (without a [!]).
Also, the plugin only takes a single character (e.g. ( to surround with parens and whitespace, ) to surround with just parens); you can reference that in your custom command via <args>.
You create a custom command with :help :command; note that the command has to start with an uppercase letter:
command! -nargs=+ Surround execute "normal gv\<Plug>VSurround" . <q-args>
This re-enters visual mode (gv), then invokes the plugin, and finally supplies the passed argument. In order to directly invoke it from visual mode, just add a -range to the definition, so that the command takes (and ignores) the '<,'> range that Vim automatically supplies then.
You can then invoke the new custom command as :Surround ), for example, and it will surround the previous visual selection with (...).
It's not exactly what you asked, but since the problem is memorizing, how about putting this in your .vimrc:
vmap ( o<ESC>i(<ESC>gvo<ESC>a)<ESC>
vmap " o<ESC>i"<ESC>gvo<ESC>a"<ESC>
Make your selection, hit ( and it gets surrounded with ( ).
Make your selection, hit " and it gets surrounded with " ".
You could do the same with {, [ or '. For such a simple use-case there's no real need for a plugin.

Search and replace contents of yanked text in vim

In my vimrc, I have a shortcut to copy the filename with its path to the clipboard.
:nmap cp :let #* = expand("%")
This works fine. Now, I want to replace the contents of this yanked text to
1) replace \ with /
2) remove certain words from the yanked text.
I am familiar with search and replace on regular text but I don't know how to change my vimrc entry to do this every time on the yanked text when I use my shortcut.
So, something like this?
:nmap cp :let #* = expand("%") || %s/\\/\/ || %s/<word>//
I am using gvim on windows.
The :substitute command works on the buffer contents itself; that's not so useful here. (You could temporarily :put the register / file name, transform it, and then :delete it (back) into a register.) Fortunately, there's an equivalent low-level :help substitute() function that you can apply on a String:
:nnoremap cp :let #* = substitute(expand("%"), '\\', '/', 'g')<CR>
In fact, expand() directly supports a special substitution :help filename-modifiers, so this would be a (rather obscure) solution, too:
:nnoremap cp :let #* = expand("%:gs?\\?/?")<CR>
For the additional removal of <word>, you can use another layer of substitute() / append another :gs???. I'll leave that to you.
Additional critique
Your mapping is missing the trailing <CR> to conclude command-line mode.
You should use :noremap; it makes the mapping immune to remapping and recursion.
% is relative to the current working dir. If you need the full absolute path, you can get that via %:p.
Starting your mapping with c is unconventional. It's fine here, because the change command wants a {motion}, and p is not a valid one. In general, I'd rather avoid such clever overloading (as long as you have other unused mapping prefixes still available).

vim: Bind leader key to open .vimrc in a different path

I have my .vimrc in a different path, that I source from my main ~/.vimrc (so I can share same settings across Windows, bash on Windows, etc).
I'm trying to write something in the .vimrc in question, that would make a hotkey for editing said .vimrc, without hard coding the path.
What I currently have is this:
let g:vimrc_path = expand('<sfile>')
:map <Leader>v exec(":e " + g:vimrc_path + "<CR>")
But this doesn't seem to do anything. I've verified that g:vimrc_path is the right value, and that the <Leader>v ends up being called by subbing in echo messages, but I'm not wrapping my head around why the variable I'm trying to define doesn't get expanded correctly.
String concatenation is done with ., not with +, which performs coercion into numbers and addition. But :execute takes multiple arguments (which it space-separates), so you don't actually need this here.
You should use :noremap; it makes the mapping immune to remapping and recursion.
Also, I doubt you need visual and operator-pending modes (:help map-modes), so define this just for normal mode.
:exec[ute is an Ex command, so for a normal-mode mapping, you need to first enter command-line mode. So :exec 'edit' instead of exec ':edit'.
Also, this is not a function (though Vim 8 now also has execute()), so the parentheses are superfluous.
The <silent> avoids the printing of the whole command (you'll notice the loading of the vimrc file, anyway); it's optional.
The fnameescape() ensures that pathological path names are also handled; probably not necessary here.
let g:vimrc_path = expand('<sfile>')
nnoremap <silent> <Leader>v :execute 'edit' fnameescape(g:vimrc_path)<CR>
Alternative
As the script path is static, you can move the variable interpolation from runtime (mapping execution) to mapping definition, and get rid of the variable:
execute 'nnoremap <Leader>v :edit' fnameescape(expand('<sfile>')) . '<CR>'
Strings in vimscript are concatenated with ., not with +. For example:
:echo "Hello"." world!"
will echo
Hello world!
If you were to type
:echo "Hello" + " world!"
vim would echo
0
This is because the + operator is only for numbers, so vim attempts to cast these strings to numbers. If you were to run
:echo "3" + "1"
vim would echo "4".
So basically, you just need to change
:map <Leader>v exec(":e " + g:vimrc_path + "<CR>")
to
:map <Leader>v exec(":e ".g:vimrc_path."<CR>")
Another problem you might have not seen is that "<CR>" evaluates to the literal text "<CR>", so it only messes up your function. If you want a literal carriage return, you would need a backslash. However, you definitely do not want to do this! Seriously, try it out and see.
You can see the issue. It looks for a file that has a literal carriage return at the end of the filename! There is a very simple fix though. Remove the "\<cr>" completely. Since :exec runs ex commands by default, the carriage return (and the colon too for that matter) are unnecessary.
Also, as a nitpick,
The parenthesis are not needed for the "exec" function, and
Use nnoremap instead to avoid recursive mappings.
Taking all of this into consideration, I would simplify it to
:nnoremap <Leader>v :exec "e ".g:vimrc_path<cr>

How to jump to a search in a mapped :normal command?

What do you need to properly jump to a matched search result?
To reproduce, make a macro with a search in it after you've run vim -u NONE to ensure there's no vimrc interfering. You'll need to make a file with at least 2 lines and put the cursor on the line without the text TEST_TEXT.
map x :norm gg/TEST_TEXT^MIthis
My intention is that when I press x, it goes to the top of the file, looks for TEST_TEXT and then puts this at the start of the line that matches the search. The ^M is a literal newline, achieved with the CtrlQ+Enter keypress. What's happening instead is either nothing happens, or the text gets entered on the same line as when I called the macro.
If I just run the :norm gg/TEST_TEXT^MIthis command without mapping it to a key, the command executes successfully.
I had an initially longer command involving a separate file and the tcomment plugin, but I've gotten it narrowed down to this.
What is the correct sequence of keys to pull this off once I've mapped it to a key?
The problem is that the ^M concludes the :normal Ex command, so your search command is aborted instead of executed. The Ithis is then executed outside of :normal.
In fact, you don't need :normal here at all. And, it's easier and more readable to use the special key notation with mappings:
:map x gg/TEST_TEXT<CR>Ithis
If you really wanted to use :normal, you'd have to wrap this in :execute, like this:
:map x :exe "norm gg/TEST_TEXT\<lt>CR>Ithis"<CR>
Bonus tips
You should use :noremap; it makes the mapping immune to remapping and recursion.
Better restrict the mapping to normal mode, as in its current form, it won't behave as expected in visual and operator-pending mode: :nnoremap
This clobbers the last search pattern and its highlighting. Use of lower-level functions like search() is recommended instead.
There are many ways of doing this however this is my preferred method:
nnoremap x :0/TEST_TEXT/norm! Itest<esc>
Explanation:
:{range}norm! {cmd} - execute normal commands, {cmd}, on a range of lines,{range}.
! on :normal means the commands will not be remapped.
The range 0/TEST_TEXT start before the first line and then finds the first matching line.
I have a few issues with your current mapping:
You are not specifying noremap. You usually want to use noremap
It would be best to specifiy a mode like normal mode, e.g. nnoremap
It is usually best to use <cr> notation with mappings
You are using :normal when your command is already in normal mode but not using any of the ex command features, e.g. a range.
For more help see:
:h :map
:h :norm
:h range
try this mapping:
nnoremap x gg/TEST_TEXT<cr>Ithis<esc>
note that, if you map x on this operation, you lost the original x feature.

How to create an alias for ctags in vimscript

I have a function that is invoked based on a simple key mapping
function! JumpToDefinition()
let filetype=&ft
if filetype == 'coffee'
exe '<C-]>'
endif
endfunction
This works when I manually do <C-]> but when I try to "exe" it above I get a "trailing whitespace" error.
How can I invoke this in a standalone function like I have above?
Note that :execute runs the resulting expression as an Ex command,
which isn't probably what you want since there is no <C-]> Ex command. You
should be using :normal.
However, to be able to use these "special keys", instead of the characters
they represent, you have to pay attention to three things:
The correct way to represent them is with a backslash \<xxx>. Check the
help for expr-string.
Use double quotes, not single quotes
:normal accepts commands, not an expression like :execute
So, from items 1 and 2 above we know that "\<C-]>" should be used, but you
can't put this in front of :normal. Well, you can, but then it will be
executed as "quote, backslash, C, ...". The solution is to go back to using
:execute, this time to build the string with the visible "\<xxx>" in front
of :normal, which will be expanded to the actual character and executed.
:exe "norm \<C-]>"

Resources