问题
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.
回答1:
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
回答2:
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
回答3:
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.
来源:https://stackoverflow.com/questions/57854158/how-to-make-vim-s-vimgrep-command-keep-indentations