According to this official guide it is possible to determine your own editors that are called per the filename suffix when in VIM we use command g + x (while cursor is over a file name or URI).
This partially works for me. I used this block of code in the ~/.vimrc file:
" make sure that viewer is selected according to the suffix.
let g:netrw_browsex_viewer="-"
" functions for file extension '.md'.
function! NFH_md(filename)
typora filename
endfunction
" functions for file extension '.pdf'.
function! NFH_pdf(filename)
zathura filename
endfunction
Now I use Vim to open a source file e.g. main.c and navigate to these two line comments:
// EXAMPLE: ../../001--documentation/motorola--SREC_format.pdf
// EXAMPLE: ./markdown.md
If I hover with the cursor over the 1st one and press g + x I get this error:
Not an editor command: zathura filename
If I hover with the cursor over the 2nd one and press g + x I get this error:
Not an editor command: typora filename
So it looks like functions are executed according to a file suffix (this is good), but why isnt a file opened? I am probably missing the knowledge on how to properly pass an argument to the funmctions? What should I do to sucessfuly open the files with editors zathura and typora (both are installed on my system and can be run from the terminal)?
After #phd suggestions I tried this:
" make sure that viewer is selected according to the suffix.
let g:netrw_browsex_viewer="-"
" functions for file extension '.md'.
function! NFH_md(filename)
:! typora filename
endfunction
" functions for file extension '.md'.
function! NFH_pdf(filename)
:! zathura filename
endfunction
Which now opens the programs but it searches for the filename and can't find it. So function doesn't get the argument properly...
Errors are now:
error: Unknown file type: 'cannot open `/home/ziga/Dropbox/workspace/racunalnistvo/projects--pistam/2021-01-06--programmer_migration/002--sw/006/filename' (No such file or directory)'
and:
ENOENT: no such file or directory, open '/home/ziga/.../002--sw--006/filename
Also I read here that ! is not asynchronous and !start is a Windows only. I need Linux solution which is asynchronous.
The answer is:
" make sure that viewer is selected according to the suffix.
let g:netrw_browsex_viewer="-"
" functions for file extension '.md'.
function! NFH_md(f)
execute '!typora' a:f
endfunction
" functions for file extension '.pdf'.
function! NFH_pdf(f)
execute '!zathura' a:f
endfunction
If execute is not used then a:f is not expanded.
Note:
This is not asynchronous solution!
For an asynchronous solution we can install vim plugin asyncrun and change the above code to this:
" make sure that viewer is selected according to the suffix.
let g:netrw_browsex_viewer="-"
" functions for file extension '.md'.
function! NFH_md(f)
call asyncrun#run("", "cwd", "typora " . a:f)
endfunction
" functions for file extension '.pdf'.
function! NFH_pdf(f)
call asyncrun#run("", "cwd" , "evince " . a:f)
endfunction
" functions for file extension '.html'.
function! NFH_html(f)
call asyncrun#run("", "cwd", "firefox --new-window " . a:f)
endfunction
TODO:
With this there are very little annoying problems left. For example
URI's ending in .com, .org, gov, php... aren't opened as it is
virtually impossible to create a handler function for every possible
top domain! There is another problem, because URI's to a PDF files
that end with #page=100 or #some_chapter aren't opened. Similar is
with markdown source files where '#some_chapter' is ignored.
Here is a quick reference works and what does not (copy in vim,
hoover over the URI / file and press g+x):
// ✔ EXAMPLE (g + x): ./markdown.md
// ✘ EXAMPLE (g + x): ./markdown.md#test
// ✔ EXAMPLE (g + x): ../../001--documentation/motorola--SREC_format.pdf
// ✘ EXAMPLE (g + x): ../../001--documentation/stm32--boot_mode_and_bootloader.pdf#page=2
// ✔ EXAMPLE (g + x): ../../001--documentation/C_--_HTML_documentation/en/index.html
// ✔ EXAMPLE (g + x): https://learnvimscriptthehardway.stevelosh.com/chapters/23.html
// ✘ EXAMPLE (g + x): https://www.google.com
// ✔ EXAMPLE (g + x): https://www.google.com/index.html
// ✘ EXAMPLE (g + x): https://wiki.archlinux.org/index.php/HiDPI#GNOME
// ✘ EXAMPLE (g + x): file:///home/ziga/Dropbox/workspace/racunalnistvo/projects/2021-01-06--programmer_migration/001--documentation/stm32--usart_protocol_used_to_talk_with_bootloader.pdf
// ✘ EXAMPLE (g + x): file:///home/ziga/Dropbox/workspace/racunalnistvo/projects/2021-01-06--programmer_migration/001--documentation/stm32--usart_protocol_used_to_talk_with_bootloader.pdf#page=10
If anyone finds a way to fix these problems or can write a vim plugin
capable of opening these URIs it would be wonderful!
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
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.
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>
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.