vim fzf get selected word as --query parameter - vim

I'm actually searching to make fuzzy search with :FZF in vim, but with parameter like this :FZF -q /tmp/boulou
But, replace the /tmp/boulou by the words selected in the visual mode and bind it to a vmap.
Have you got an idea to help me ?
Thanks, kind regards

Accordingly to xolox answer, you can create mapping like this:
function! s:getVisualSelection()
let [line_start, column_start] = getpos("'<")[1:2]
let [line_end, column_end] = getpos("'>")[1:2]
let lines = getline(line_start, line_end)
if len(lines) == 0
return ""
endif
let lines[-1] = lines[-1][:column_end - (&selection == "inclusive" ? 1 : 2)]
let lines[0] = lines[0][column_start - 1:]
return join(lines, "\n")
endfunction
vnoremap <silent><leader>f <Esc>:FZF -q <C-R>=<SID>getVisualSelection()<CR><CR>
Keep in mind, it will work only for word selection. It will not work for multilines or selection which contains tab characters. You just need to call a function instead of a command but since I do not have fzf installed you have to do this by yourself.
In case you need to search the word under the cursor (in normal mode):
nnoremap <silent><leader>f :FZF -q <C-R>=expand("<cword>")<CR><CR>

Related

fzf.vim: fuzzy matching with a fall-back source

I would like to implement a vim command to select buffers with the following behaviour:
When called, it present the user with a list of loaded buffers and other recently used buffers from the current directory (This is different from the :History command provided by fzf.vim in that we list only the recently used buffers from the current directory, fzf.vim lists all the recent buffers). The user can search for the file name they would like to load
If none of the options matches user's search term, expand the scope of the search by listing all the files in the current directory and letting the user fuzzy search through them.
This is what I have so far (This assumes that junegunn/fzf.vim is already installed):
nnoremap <silent> <space><space> :call <SID>recent_files()<CR>
function! s:recent_files_sink(items)
if len(a:items) == 2
execute "edit" a:items[1]
return
endif
call fzf#vim#files("", {'options': ['--query', a:items[0]]})
endfunction
" deduped is a list of items without duplicates, this
" function inserts elements from items into deduped
function! s:add_unique(deduped, items)
let dict = {}
for item in a:deduped
let dict[item] = ''
endfor
for f in a:items
if has_key(dict, f) | continue | endif
let dict[f] = ''
call add(a:deduped, f)
endfor
return a:deduped
endfunction
function! s:recent_files()
let regex = '^' . fnamemodify(getcwd(), ":p")
let buffers = filter(map(
\ getbufinfo({'buflisted':1}), {_, b -> fnamemodify(b.name, ":p")}),
\ {_, f -> filereadable(f)}
\ )
let recent = filter(
\ map(copy(v:oldfiles), {_, f -> fnamemodify(f, ":p")}),
\ {_, f -> filereadable(f) && f =~# regex})
let combined = s:add_unique(buffers, recent)
call fzf#run(fzf#wrap({
\ 'source': map(combined, {_, f -> fnamemodify(f, ":~:.")}),
\ 'sink*': function('s:recent_files_sink'),
\ 'options': '--print-query --exit-0 --prompt "Recent> "'
\ }))
endfunction
SpaceSpace invokes s:recent_files() which lists loaded buffers and recently used files from viminfo. The interesting bit here is the sink* option in the call to fzf#run (4th line from the bottom). The sink is another function. If a filename was selected, the sink function loads it for editing, otherwise, it calls fzf#vim#files, which lists the contents of the directory.
This is pretty close to what I want but there are a couple of problems:
When no matches are found in recent files, the user must press Return to trigger the fall-back. (One can easily argue that this is the correct behaviour)
When the fall-back fzf window is loaded, it starts in the normal mode and not the insert mode
The user must enter the search query again in the new fzf window (solved)
I'm looking for suggestions on how to solve these problems, particularly 2 and 3. I'm also open to solutions that don't meet the specifications exactly but provide a good user experience.
EDIT: I came up with another approach to achieve this using this as the source for fzf
cat recent_files.txt fifo.txt
where recent_files.txt is a list of recent files and fifo.txt is an empty fifo created using mkfifo. A mapping can be added to buffer which triggers a command to write to the fifo. This way, the user can use that mapping to include a list of all files in case they don't find a match in recent files. This approach becomes problematic in cases where user finds the file in recents and presses enter. Since fzf is till waiting to read from fifo, it does not exit https://github.com/junegunn/fzf/issues/2288
I was finally able to come to a solution that is pretty close using fzf's reload functionality. (Thanks to junegunn)
This is how it goes:
nnoremap <silent> <space><space> :call <SID>recent_files()<CR>
" Initialize fzf with a list of loaded buffers and recent files from
" the current directory. If <space> is pressed, we load a list of all
" the files in the current directory
function! s:recent_files()
let regex = '^' . fnamemodify(getcwd(), ":p")
let buffers = filter(map(
\ getbufinfo({'buflisted':1}), {_, b -> fnamemodify(b.name, ":p")}),
\ {_, f -> filereadable(f)}
\ )
let recent = filter(
\ map(copy(v:oldfiles), {_, f -> fnamemodify(f, ":p")}),
\ {_, f -> filereadable(f) && f =~# regex})
let combined = <SID>add_unique(buffers, recent)
"-------------------------------------------
" This is the key piece that makes it work
"-------------------------------------------
let options = [
\ '--bind', 'space:reload:git ls-files',
\ ]
call fzf#run(fzf#wrap({
\ 'source': map(combined, {_, f -> fnamemodify(f, ":~:.")}),
\ 'options': options
\ }))
endfunction
" deduped is a list of items without duplicates, this
" function inserts elements from items into deduped
function! s:add_unique(deduped, items)
let dict = {}
for item in a:deduped
let dict[item] = ''
endfor
for f in a:items
if has_key(dict, f) | continue | endif
let dict[f] = ''
call add(a:deduped, f)
endfor
return a:deduped
endfunction
I start FZF by using <space><space>. This FZF window contains only the most recent files from the current directory. If I then press <space>, the FZF window is updated with a new list of files obtained from git ls-files.

Error while calling sort on a list of quickfix items even though the list items are copied to a local variable

Below error is observed while calling TestQFItem even though list items are copied to a local variable using let itemsCopied = deepcopy(items):
Error detected while processing function TestQFItem:
line: 3
E:21 cannot make changes, modifiable is off:^Isort(itemscopied,function("CompareQuickFixItems"))
Even though the list items are copied to a local variable using deepcopy why vim is throwing the error "E:21 cannot make changes, modifiable is off"?
function! CompareQuickFixItems(arg1, arg2)
if a:arg1['lnum'] == a:arg2['lnum']
return 0
elseif a:arg1['lnum'] < a:arg2['lnum']
return -1
else
return 1
endif
endfunction
function! TestQFItem()
let items = getqflist()
let itemsCopied = deepcopy(items)
call sort(itemsCopied, function("CompareQuickFixItems"))
call setqflist(itemsCopied, "r")
endfunction

How to make vim’s vimgrep command keep indentations?

I’m trying to use vim’s quick fix (or local) list to get some information extracted from a file. For example, I want to get all the method names of a python module (the idea was borrowed from pycharm). What I want to get in vim’s “local list” is just something like the following:
class Foo:
def one():
def two():
def three():
def bar():
def bazz():
To achieve that, I do approximately the following steps:
:" OK, the current buffer is being used.
:let file_name = expand('%:p')
:" The heart of the process is one of vim’s grep-like command.
:execute 'lvimgrep /\v^\s*(class|def)/ '.file_name
:" I open the results with the “lopen” command because “llist”
:" doesn’t allow me to use concealing.
:lopen
:" Since I’m working with one file, I don’t need information
:" about file name, line number etc.
:setlocal conceallevel=3
:syntax match NonText /\v^.+col \d+([:]|[|])/ transparent conceal
:" Please note, I‘m still able to jump to a line
:" with the “ll” command.
But unfortunately I get:
class Foo:
def one():
def two():
def three():
def bar():
def bazz():
All the indents are swallowed! The result is quite useless… I can’t differentiate which of the functions belong to a class, which of them are stand-alone.
Please note, the concealing doesn’t have a meaningful influence on the result. If I took away the two last commands (conceal-related), nothing significant would change, only the file name and line/column numbers would be shown but the text in the lines would be still without indents anyway.
So, my questions are:
Is it possible to make lvimgrep (or an analogue) keep the lines untouched in order to save indentation? Is there a magic command or option to do that? Or should I program my own implementation of lvimgrep?
P.S. I’d like to use vim’s regular expressions. But if it’s impossible, I could switch to the external “grep” command (I’m a linux guy) and use the BRE or ERE syntax as well.
No, currently, it is impossible to make lvimgrep (or even similar commands) keep leading whitespace characters in the quickfix (location) list entries, since space and tab characters are unconditionally skipped from the beginning, if the text length is greater than 3.
The only way to achieve the desired behavior (at least, using *vimgrep commands) is to modify the source code. For example, you might add an option as demonstrated in the following patch:
diff --git a/runtime/optwin.vim b/runtime/optwin.vim
index 7d3a8804d..caac55cf2 100644
--- a/runtime/optwin.vim
+++ b/runtime/optwin.vim
## -1299,6 +1299,7 ## call <SID>OptionG("ve", &ve)
call append("$", "eventignore\tlist of autocommand events which are to be ignored")
call <SID>OptionG("ei", &ei)
call append("$", "loadplugins\tload plugin scripts when starting up")
+call append("$", "locws\tenables whitespace characters for entries in the location window")
call <SID>BinOptionG("lpl", &lpl)
call append("$", "exrc\tenable reading .vimrc/.exrc/.gvimrc in the current directory")
call <SID>BinOptionG("ex", &ex)
diff --git a/src/option.c b/src/option.c
index aabfc7f53..4ba280806 100644
--- a/src/option.c
+++ b/src/option.c
## -1791,6 +1791,9 ## static struct vimoption options[] =
{"loadplugins", "lpl", P_BOOL|P_VI_DEF,
(char_u *)&p_lpl, PV_NONE,
{(char_u *)TRUE, (char_u *)0L} SCTX_INIT},
+ {"locws", NULL, P_BOOL|P_VI_DEF,
+ (char_u *)&p_locws, PV_NONE,
+ {(char_u *)FALSE, (char_u *)0L} SCTX_INIT},
{"luadll", NULL, P_STRING|P_EXPAND|P_VI_DEF|P_SECURE,
#if defined(DYNAMIC_LUA)
(char_u *)&p_luadll, PV_NONE,
diff --git a/src/option.h b/src/option.h
index c1a25b342..5e17c459e 100644
--- a/src/option.h
+++ b/src/option.h
## -602,6 +602,7 ## EXTERN char_u *p_lcs; // 'listchars'
EXTERN int p_lz; // 'lazyredraw'
EXTERN int p_lpl; // 'loadplugins'
+EXTERN int p_locws; // 'locws'
#if defined(DYNAMIC_LUA)
EXTERN char_u *p_luadll; // 'luadll'
#endif
diff --git a/src/quickfix.c b/src/quickfix.c
index 136c472e1..8e206ddd7 100644
--- a/src/quickfix.c
+++ b/src/quickfix.c
## -4417,8 +4417,9 ## qf_update_buffer(qf_info_T *qi, qfline_T *old_last)
static int
qf_buf_add_line(buf_T *buf, linenr_T lnum, qfline_T *qfp, char_u *dirname)
{
- int len;
- buf_T *errbuf;
+ int len;
+ buf_T *errbuf;
+ long lval;
if (qfp->qf_module != NULL)
{
## -4472,10 +4473,12 ## qf_buf_add_line(buf_T *buf, linenr_T lnum, qfline_T *qfp, char_u *dirname)
IObuff[len++] = '|';
IObuff[len++] = ' ';
- // Remove newlines and leading whitespace from the text.
+ // Remove newlines and leading whitespace from the text,
+ // if the user not enabled whitespaces explicitly via locws option.
// For an unrecognized line keep the indent, the compiler may
// mark a word with ^^^^.
- qf_fmt_text(len > 3 ? skipwhite(qfp->qf_text) : qfp->qf_text,
+ get_option_value((char_u *)"locws", &lval, NULL, 0);
+ qf_fmt_text(len > 3 ? (lval ? qfp->qf_text : skipwhite(qfp->qf_text)) : qfp->qf_text,
IObuff + len, IOSIZE - len);
if (ml_append_buf(buf, lnum, IObuff,
With locws option, you could enable whitespace characters in the quickfix/location entries as follows:
:set locws
Alternative Option
As an alternative, you could just list out the results via :# an :global
:g/\v^\s*(class|def)/#
This will print out the relevant lines with their associated line numbers.
A slightly fancier mapping:
nnoremap <leader>f :keeppatterns g/\v^\s*(class|def)/#<cr>:
With this mapping you can just type the line number and press enter to jump to a line after executing the mapping.
For more help see:
:h :g
:h :#
:h :keeppatterns
:h :range
Using Quickfix List
In order to use the quickfix list you will need to "mangle" your indent text with another character, e.g. >.
command! PyLocations call <SID>py_locations()
function! s:py_locations()
let lst = []
let bufnr = bufnr('%')
let pat = repeat(' ', shiftwidth())
let Fn = {l -> substitute(matchstr(l, '^\s*'), pat, '▶', 'g') . matchstr(l, '\S.*')}
keeppatterns g/\v^\s*(class|def)>/call add(lst, {'bufnr': bufnr, 'lnum': line('.'), 'text': call(Fn, [getline('.')])})
call setqflist(lst, ' ')
cwindow
endfunction
I’ve done it! But it took more than “half an hour” as I supposed early.
During the research, I found that vim’s local list (and I‘m sure quick fix list too) keeps the indentation of a line when it’s unable to recognize the line as a valid “goto information”, when the line format doesn’t correspond to errorformat. (See :help quickfix-valid) So, to get a nice looking list it must be rendered manually. But in order to have the possibility of jumping to the items of a search result, a quickfix or local list must be created as well.
I’ve split the task into two functions: the fist one retrieves the data, the second one shows it.
function! s:grep_buffer(pattern)
let file_name = expand("%")
let b:grepped = [] |" It will store the search results.
lexpr! [] |" The local list will give the possibility of jumping.
for line_number in range(1, line('$'))
let line_content = getline(line_number)
if line_content =~ '\V'.a:pattern
call add(b:grepped, line_content)
laddexpr file_name.':'.line_number.':'.line_content
endif
endfor
endfunction
function! s:show_result()
if exists('b:grepped')
let grepped = b:grepped |" After creation a new window it’ll be lost.
vnew
call append(0, grepped)
setlocal buftype=nofile |" Don’t make vim save the content.
setlocal noswapfile
setlocal nomodifiable
nn <silent> <buffer> <CR> :exe line(".").'ll'<CR>
wincmd l |" Now the old window is on the right.
hide
endif
endfunction
Of course, a convenient key mapping must be designed. (There is a trailing space in the second line.)
command! -nargs=1 GrepBuffer call <SID>grep_buffer(<f-args>)
nn <leader>g :GrepBuffer
nn <silent> <leader>s :call <SID>show_result()<CR>
It’s super convenient! When I want to overview the search results again, I call the show_result function which replaces the current window with the search results. I can use all the usual navigation tools to move the cursor through the search results. And all I need to jump to an interesting place is just to hit the enter key!
Thanks for all! The problem is solved, vim is the greatest editor.

Get filename and line number that called vim autoload function

I'm trying to populate the quickfix list using an autoload function, i.e.:
function! myplugin#myfunc(msg)
" this doesn't work from *inside* an autoload function
let filename = fnamemodify(resolve(expand('<sfile>:p')))
" not sure if it's possible to get the line number from where
" a function was called
let linenum = '?#'
" create qf dict object
" set filename, line number, bufnr, text, etc
" add dict to qflist
" setqflist(qfdictlist)
endfunction!
The problem I've run into is I can't figure out a way to get the filename and line number from the code that called the autoload function. Any suggestions?
Out of the box. This is not possible.
However, depending on the exact scenario, here are a few leads.
I've attempted to write a function that decodes the callstack from v:throwpoint when an exception is caught. It's still experimental. See https://github.com/LucHermitte/lh-vim-lib/blob/master/autoload/lh/exception.vim
From my test unit framework, I know precisely which test-file/line is failing. To do so, I had to parse the UT file in order to inject the line number of the caller into the :Assert* commands.
As you see, none of these solutions is very good. But there are none other right now. The callstack isn't available, except from v:throwpoint in an exception context. The only other solution is to have callers inject their references (~__FILE__ + ~__LINE__) when calling. And the only way to automate this is to compile the caller script into another script that automatically injects the missing information.
By right now, understand there had been a proposal on vim-dev mailing list this last month in order to permit to have access to the call stack, but alas, only during debugging sessions: https://github.com/vim/vim/pull/433
If this is accepted, may be it could be extended later to offer a viml function that'll export this information.
EDIT: Your question inspired me to write a simplistic logging facility for vim:
" Function: lh#log#new(where, kind) {{{3
" - where: "vert"/""/...
" - kind: "qf"/"loc" for loclist
" NOTE: In order to obtain the name of the calling function, an exception is
" thrown and the backtrace is analysed.
" In order to work, this trick requires:
" - a reasonable callstack size (past a point, vim shortens the names returned
" by v:throwpoint
" - named functions ; i.e. functions defined on dictionaries (and not attached
" to them) will have their names mangled (actually it'll be a number) and
" lh#exception#callstack() won't be able to decode them.
" i.e.
" function s:foo() dict abort
" logger.log("here I am");
" endfunction
" let dict.foo = function('s:foo')
" will work correctly fill the quicklist/loclist, but
" function dict.foo() abort
" logger.log("here I am");
" endfunction
" won't
" TODO: add verbose levels
function! lh#log#new(where, kind) abort
let log = { 'winnr': bufwinnr('%'), 'kind': a:kind, 'where': a:where}
" open loc/qf window {{{4
function! s:open() abort dict
try
let buf = bufnr('%')
exe 'silent! '.(self.where). ' '.(self.kind == 'loc' ? 'l' : 'c').'open'
finally
call lh#buffer#find(buf)
endtry
endfunction
" add {{{4
function! s:add_loc(msg) abort dict
call setloclist(self.winnr, [a:msg], 'a')
endfunction
function! s:add_qf(msg) abort dict
call setqflist([a:msg], 'a')
endfunction
" clear {{{4
function! s:clear_loc() abort dict
call setloclist(self.winnr, [])
endfunction
function! s:clear_qf() abort dict
call setqflist([])
endfunction
" log {{{4
function! s:log(msg) abort dict
let data = { 'text': a:msg }
try
throw "dummy"
catch /.*/
let bt = lh#exception#callstack(v:throwpoint)
if len(bt) > 1
let data.filename = bt[1].script
let data.lnum = bt[1].pos
endif
endtry
call self._add(data)
endfunction
" reset {{{4
function! s:reset() dict abort
call self.clear()
call self.open()
return self
endfunction
" register methods {{{4
let log.open = function('s:open')
let log._add = function('s:add_'.a:kind)
let log.clear = function('s:clear_'.a:kind)
let log.log = function('s:log')
let log.reset = function('s:reset')
" open the window {{{4
call log.reset()
return log
endfunction
Which use this other function of mine that decodes the callstack.
Well, if you can't get the file and line from inside the autoload function, you have to pass that into your function.
Your autoloaded function is invoked somehow, either by a custom mapping, command, or on an :autocmd event. From there, you can resolve the current file (expand('%') and line number ('line('.')) and pass that in.
But why do you need that?! For normal editing tasks, I can't imagine why. If you're writting a custom Vim debugging plugin, okay, that might be useful. But debugging via Vimscript is difficult (as you've found out), the callstack isn't exposed. Better stick with the built-in :debug and :breakadd; I've so far found them sufficient.

How to get path to the current vimscript being executed

In my vim plugin, I have two files:
myplugin/plugin.vim
myplugin/plugin_helpers.py
I would like to import plugin_helpers from plugin.vim (using the vim python support), so I believe I first need to put the directory of my plugin on python's sys.path.
How can I (in vimscript) get the path to the currently executing script? In python, this is __file__. In ruby, it's __FILE__. I couldn't find anything similar for vim by googling, can it be done?
Note: I am not looking for the currently edited file ("%:p" and friends).
" Relative path of script file:
let s:path = expand('<sfile>')
" Absolute path of script file:
let s:path = expand('<sfile>:p')
" Absolute path of script file with symbolic links resolved:
let s:path = resolve(expand('<sfile>:p'))
" Folder in which script resides: (not safe for symlinks)
let s:path = expand('<sfile>:p:h')
" If you're using a symlink to your script, but your resources are in
" the same directory as the actual script, you'll need to do this:
" 1: Get the absolute path of the script
" 2: Resolve all symbolic links
" 3: Get the folder of the resolved absolute file
let s:path = fnamemodify(resolve(expand('<sfile>:p')), ':h')
I use that last one often because my ~/.vimrc is a symbolic link to a script in a git repository.
Found it:
let s:current_file=expand("<sfile>")
It is worth mentioning that the above solution will only work outside of a function.
This will not give the desired result:
function! MyFunction()
let s:current_file=expand('<sfile>:p:h')
echom s:current_file
endfunction
But this will:
let s:current_file=expand('<sfile>')
function! MyFunction()
echom s:current_file
endfunction
Here's a full solution to OP's original question:
let s:path = expand('<sfile>:p:h')
function! MyPythonFunction()
import sys
import os
script_path = vim.eval('s:path')
lib_path = os.path.join(script_path, '.')
sys.path.insert(0, lib_path)
import vim
import plugin_helpers
plugin_helpers.do_some_cool_stuff_here()
vim.command("badd %(result)s" % {'result':plugin_helpers.get_result()})
EOF
endfunction
If you really want to get the script path inside a function (which is what I'd like to), you can still use <sfile>'s second semantic, or its equivalent <stack> inside expand().
<sfile> ...
When executing a legacy function, is replaced with the call
stack, as with <stack>
...
:<stack> <stack>
<stack> is replaced with the call stack, using
"function {function-name}[{lnum}]" for a function line
and "script {file-name}[{lnum}]" for a script line, and
".." in between items. E.g.:
"function {function-name1}[{lnum}]..{function-name2}[{lnum}]"
If there is no call stack you get error E489 .
However you possibly don't want to use it in a plugin, as you can use autoload functions in plugin, using this relative#path#to#plugin#root#script notation.
I use this for sourcing purpose:
function! s:SourceLocal(script)
let l:callstack = expand("<stack>")
let l:list = split(l:callstack, '\.\.')
" list[-1] is SourceLocal function itself
" list[-2] is the calling script
let l:script_name = matchstr(l:list[-2], '^\(script \)\=\zs.\+\ze\[\d\+\]$')
let l:script_path = fnamemodify(l:script_name, ":p:h")
" l:script_path is the path where the script calling this function resides
execute printf("source %s/%s", l:script_path, a:script)
endfunction
command! -nargs=1 SourceLocal :call s:SourceLocal(<f-args>)
Then you can SourceLocal inside any script to source another script relative to it.

Resources