How to access script-variable in mapping? - vim

I want to do following behaviour
let s:cmd = “echo ‘here is a long command’”
map aa :execute s:cmd<cr>
“ error, mapping cannot access script variable
map bb :execute <SID>cmd<cr>
“ error no variable named <SNR>…cmd
How to access the script variable in mapping?

You can provide a script local function and then access to the variable through that function.
let s:var = 'foo'
function! s:get(key) abort
return get(s:, a:key)
endfunction
nnoremap <silent> µ :<c-u>echo <sid>get('var')<cr>

Related

Vim - Overwrite Plugin Scoped Function

I've a plugin in Vim and I don't like the behavior of a single function within it. But it isn't rly a case to open a pull request, but more an extension for it.
I know that overwriting a function ist possible by using a bang as postfix, as soon as the new definition comes after the previous one. But how can I do such thing, if this method is scoped to a script within a plugin?
I wasn't able to find a hint in _Vim_s help, nor by request a search engine. Anybody aware of this topic, at least if he can say that it is simply not possible.
A short example:
plugin/autoload/plugin.vim
...
function! s:foo() {
// behavior I would like to adjust
}
...
~/.vimrc
function! foo() {
// the "correct" behavior
}
Thanks for any help!
Actually it is possible. But as #romainl said, you'd better suggest your patch to the plugin maintainer or ask for a variation point.
Regarding the how.
First, you'll need to identify the script number of this autoload plugin. Let's say that :scriptname says it's 210. In order to do that automatically I have a lh#askvim#scriptid() function in my library plugin that does the job -- see the current definition at the end of the answer.
Then, to override the s:foo() function, you'll need to provide a new definition for
function! <SNR>210_Foo()
new definition
endfunction
(I've just tested it with vim 8.0-1157)
IOW, we can override a script-local function. However, I haven't found how to override a script-local variable directly without a reference to its s: dictionary. We could inject setter/getter functions to a specific variable or a function that returns the local s: dictionary.
lh#askvim#scriptid() current definition is the following
" Function: lh#askvim#execute(command) {{{3
" #since Version 4.0.0
if exists('*execute')
function! lh#askvim#execute(command) abort
return split(execute(a:command), "\n")
endfunction
else
function! lh#askvim#execute(command) abort
return s:beware_running_through_client_server ? [] : split(lh#askvim#exe(a:command), "\n")
endfunction
endif
" Function: lh#askvim#scriptnames() {{{3
function! lh#askvim#scriptnames() abort
let scripts = lh#askvim#execute('scriptnames')
let s:scripts = map(copy(scripts), 'split(v:val, "\\v:=\\s+")')
call lh#list#map_on(s:scripts, 1, 'fnamemodify(v:val, ":p")')
return s:scripts
endfunction
" Function: lh#askvim#scriptid(name) {{{3
function! lh#askvim#scriptid(name, ...) abort
let last_change = get(a:, 1, 0)
if last_change || !exists('s:scripts')
call lh#askvim#scriptnames()
endif
let matches = filter(copy(s:scripts), 'v:val[1] =~ a:name')
if len(matches) > 1
throw "Too many scripts match `".a:name."`: ".string(matches)
elseif empty(matches)
if last_change
throw "No script match `".a:name."`"
else
return lh#askvim#scriptid(a:name, 1)
endif
endif
return matches[0][0]
endfunction
That is not possible.
s:foo() is scoped to the script it belongs to (see :help s:) so it can't be accessed from anywhere else.
Fork it.
Make the desired changes to your fork.
Use your fork instead of the original.
Consider submitting a pull request.

Best way to create substitution macros in vim

I'd like to set up some custom auto-complete macros in vim. I'm thinking something like this (| represents the cursor position):
it<TAB>|
immediately becomes:
it("|", function () {
});
Is this possible using straight vim, or would I need a plugin? If so, is there a preferred plugin out there?
Using an abbreviation you could write something like this:
inorea it it("", function () {<cr>});<c-o>k<c-o>f"
The purpose of <c-o>k<c-o>f" at the end is to reposition your cursor inside the double quotes, but it may not work all the time.
Using a mapping, you could try this:
ino <expr> <tab> <sid>expand_last_word()
let s:your_expansions = {
\ 'it': '\<c-w>it(\"\", function () {\<cr>});\<c-o>k\<right>',
\ }
fu! s:expand_last_word() abort
let last_word = matchstr(getline('.'), '\v<\k+%'.col('.').'c')
return has_key(s:your_expansions, last_word)
\ ? eval('"'.s:your_expansions[last_word].'"')
\ : "\<tab>"
endfu
You would have to add your abbreviations and their expansions inside the dictionary s:your_expansions.
Using the :read command, you could define larger snippets of code, and split them across several files:
ino <expr> <tab> <sid>expand_last_word()
fu! s:expand_last_word() abort
let last_word = matchstr(getline('.'), '\v<\k+%'.col('.').'c')
if last_word ==# 'it'
return "\<c-w>\<c-o>:r /path/to/some_file\<cr>\<c-o>f\"\<right>"
endif
return "\<tab>"
endfu
Here /path/to/some_file should contain your snippet:
it("", function () {
});
They are very simple solutions, if you want something more robust, you probably need a snippets plugin. One of them is UltiSnips, which requires that your Vim version has been compiled with Python support (:echo has('python') or :echo has('python3') returns 1).
With UltiSnips, you would write your snippet like this:
snippet it "your description" b
it("$0", function () {
});
endsnippet
Here the definition is included between the keywords snippet and endsnippet. On the 1st line, you can describe the purpose of your snippet, inside the string in double quotes. It will be displayed by UltiSnips inside a list, if you've defined several snippets with the same tab trigger, and there's an ambiguity.
The ending b is an option to prevent the tab trigger it from being expanded anywhere else than the beginning of a line.
$0 is a tabstop, it stands for the position in which you want the cursor to be, once the snippet has been fully expanded.
The readme page on github gives a quick start, and some links to videos.
If you want to have a look at the snippets written by other people, you can install vim-snippets.
There are other snippet managers but I don't know them well enough to describe the syntax of their snippets. If you want a comparison, here's one, and here are links for some of them:
snipmate
mu-template
neosnippet
xptemplate
Here is a abbreviation that you can use for your particular example
:inoreabbrev it it("", function () {<cr>});<esc>kf"a
Typing it followed by ctrl + ] in insert mode will render
it("|", function () {
});
and keep you in insert mode.
But I would definitely go for ultisnips and there is a screencast for creating snippets on the same page. That's why I am omitting the snippet here as you can do it yourself.

abstract and reuse parts of a map command in vimrc

I wrote a map command in vim that verges on the line of being too complicated for my taste. I'd like to:
Abstract away parts, or at least spit them into multiple lines and explain each.
Reuse most of it with only 2 things, changing only 2 things in it each time.
The command uses the vim-pandoc plugin to create an Html file out of the current pandoc file and copy it to clipboard.
nnoremap <leader><leader>11 :w<cr>:Pandoc html<cr>:sleep 100m<cr>:e <c-r>=expand('%:r')<cr>.html<cr>ggVG"+y:b#<cr>:bd #<cr>
My first attempt is thus:
nnoremap <expr> <leader><leader>22 MyFunc()
func! MyFunc()
let save = ':w<cr>'
let runPandoc = ':Pandoc html<cr>'
let sleep = ':sleep 100m<cr>'
let viewResult = ':e ' . expand('%:r') . '.html' . '<cr>'
let copyAll = 'ggVG"+y'
let backToPrevBuf = ':b#<cr>'
return save . runPandoc . sleep . viewResult . copyAll . backToPrevBuf
endfunc
However, this does not work because it has <cr> in the returned value instead of actually executing enter.
Using this I can pass an arg to MyFunc that will change the html portions to other filetypes.
My two questions are:
Is there a better, more direct way of abstracting away portions of long maps?
How can I solve the issue with the <cr> appearing in the result instead of actually meaning "Press the enter button".
Thanks for the help!
EDIT:
After getting suggestions I now have the current version, it seems a bit verbose.
While I can wrap the feedkeys to be a shorter name with the 'n' parameter, is there anything else I can do?
func! MyFunc()
call feedkeys(":w\<cr>",'n')
call feedkeys(":Pandoc html\<cr>",'n')
call feedkeys(":sleep 100m\<cr>",'n')
call feedkeys(':e ' . expand('%:r') . '.html' . "\<cr>",'n')
"TODO read about :%y+ understand why it copies the entire file
call feedkeys('ggVG"+y','n')
call feedkeys(":b#\<cr>",'n')
endfunc
EDIT:
Finally, I've understood what it means to get rid of the feedkeys, and now I reached the final destination:
nnoremap <silent> <leader><leader>22 :call MyFunc()<cr><cr>
func! MyFunc()
w
Pandoc html
sleep 100m
execute 'e' (expand('%:r') . '.html')
%y+
b#
endfunc
Thanks for the help!
Is there a better, more direct way of abstracting away portions of
long maps?
Since you have already called a function, and the function just concatenate the rhs mappings, why not just do the actual work in your function, and just map to execute the function like :nnoremap whatever :call yourFunc()<cr>
How can I solve the issue with the <cr> appearing in the result
instead of actually meaning "Press the enter button".
Use "\<cr>"

Testing vim plugin private functions

I'm creating a vim plugin which has a couple of private functions and I'm trying to add unitary testing to it using vim-vspec.
What is the best way to invoke these private functions in the test file?
For now, I created a public function that invokes the private one, but I don't think that's a good approach because I'm loosing the point of having a private function. Here's some of the code
" File foo.vim (the plugin)
" Private function
fu! s:foo(arg)
...
endfu
" Public function
fu! InvokeFoo(arg)
call <SID>foo(a:arg)
endfu
" File foo-unittest.vim (the test file)
runtime! plugin/foo.vim
describe 'foo function'
it 'should have some behavior'
call InvokeFoo(...)
" Some expectations ...
end
end
I tried creating maps to the private functions but when I call exe map_combination it doesn't have any effect on the testing buffer.
I found a solution to my question here, and it gives one approach for variables and another for functions.
Variables
For the variables, I used vim's scopes. Calling :help internal-variables:
The scope name by itself can be used as a Dictionary. For example, to
delete all script-local variables:
:for k in keys(s:)
: unlet s:[k]
:endfor
So I access the :s scope by making a getter function to its dictionary:
fun! SScope()
return s:
endfu
And finally an variable s:variable will be accessed by:
let l:scope = SScope()
echom l:scope['variable']
Functions
The functions are a bit more complicated due the <SID> string. If you read the manual you'll get
When executing the map command, Vim will replace <SID> with the
special key code , followed
by a number that's unique for the script, and an underscore. Example:
:map <SID>Add could define a mapping "23_Add".
So, we need to access this unique number and one way to do it is to define a map that will serve as an accesor using maparg inside a function:
fu! SID()
return maparg('<SID>', 'n')
endfu
nnoremap <SID> <SID>
Then, to call the function we will make a little hack:
call call(substitute('s:my_function', '^s:', SID(), ''), [arg1, arg2, ...])

What is the idiomatic way to share functions between files?

I have a plugin function called InComment() stored in magic.vim *1
I would like to share it between two ftplugin files:
ftplugin/c.vim
ftplugin/python.vim
What is the idiomatic way to call InComment() function from my ftplugin files?
*1 plugin/magic.vim
function! s:InComment() " {{{
let syn = s:SyntaxName(line('.'), col('.') - 1, 1)
if syn =~? 'comment'
return 1
else
return 0
endif
endfunction "}}}
The document referenced by Prince Goulash has everything you need. To answer you specifically, you need to give the function a name based on the name of your plugin, like this:
function! magic#InComment() " {{{
let syn = s:SyntaxName(line('.'), col('.') - 1, 1)
if syn =~? 'comment'
return 1
else
return 0
endif
endfunction "}}}
Then you can call it from your plugin like this:
call magic#InComment()
The part before the # has to match the name of your plugin file. This will also cause the plugin to be autoloaded when the function is called.
I have just found this question while trying to answer it myself.
There is a Vim help section on Writing Library Scripts, which looks like exactly what we need!
(Note: I haven't actually tried it yet. I will update this post if I run into problems.)

Resources