function failed when call it from a command in vim - vim

When I find the word in the current file, I need to first type "/keyword", but I can't see all the matched rows, So I tried to use the following command to do a shortcut, but it doesn't work, could you please help check why it failed?
function! FindCurrentFile(pattern)
echo a:pattern
execute ":vimgrep" . a:pattern . " %"
execute ":cw"
endfunction
command! -nargs=1 Fi call FindCurrentFile(<args>)

By the way, if you just need a quick overview over the matches you can simply use
:g//print
or
:g//p
(You may even leave out the p completely, since :print is the default operation for the :global command.)
When the current buffer has line numbers turned off, the results produced by :g//p can be difficult to take in fast. In that case use :g//# to show the matches with the line numbers.
Another trick that works for keywords is the normal mode command [I. It shows a quick overview of all the instances of the keyword under the cursor in the current buffer. See :h [I.

try to change the line in your function into this:
execute ':vimgrep "' . a:pattern . '" ' . expand("%")

<args> is replace with the command argument as is - that means that if you write:
Fi keyword
the command will run:
call FindCurrentFile(keyword)
which is wrong - because you want to pass the string "keyword", not a variable named keyword.
What you need is <q-args>, which quotes the argument.
BTW, if you wanted more than one argument, you had to use <f-args>, which quotes multiple arguments and separates them with ,.

Related

vim mapping K to external command

I'd like to map vim's 'keywordprg' to Dash, namely, use K to do !open dash://word-unser-curse.
Currently, I'm doing this:
:let &keywordprg '!open dash://'
but it says E34: No previous command.
from :h E34:
Any '!' in {cmd} is replaced with the previous
external command (see also 'cpoptions'). But not when
there is a backslash before the '!', then that
backslash is removed. Example: ":!ls" followed by
":!echo ! \! \\!" executes "echo ls ! \!".
Thus you have to escape ! in order to have vim treat as it is, otherwise vim tries to replace it with the "previous command", resulting in the error.
Additionally, I don't think you need that ! in your keywordprg. Vim calls it as an external command anyway (the default value is man, not !man).

In Vim, how can I save a file to a path stored in a variable?

This is inside a script:
:let s:submission_path = expand("%:p:h") . '\submissions\test_submission.csv'
:echo s:submission_path
:write s:submission_path
The script raises an error "can't open file for writing", because I'm on windows and ':' is not a valid filename character. If I name the variable 'submission_path' a file with that name is written (edit: and the contents of the file are as I expect, the contents of the buffer the script is editing).
I don't understand this. The echo command before the write does what I expect it to, output the full path to the file I want to write. But write seems to ignore the variable value and use its name instead? Do I need to dereference it somehow?
Try this:
:let s:submission_path = expand("%:p:h") . '\submissions\test_submission.csv'
:echo s:submission_path
:execute "write " . s:submission_path
Execute will allow you to compose an ex command with string operations and then execute it.
vim has very screwy evaluation rules (and you just need to learn them). write does not take an expression it takes a string so s:submission_path is treated literally for write. However echo does evaluate its arguments so s:submission_path is evaluated.
To do what you want you need to use the execute command.
exec 'write' s:submission_path
Environment variables don't have this problem, but you have to dereference them like in unix with a dollar sign:
:let $submission_path = expand("%:p:h") . '\submissions\test_submission.csv'
:echo $submission_path
:write $submission_path
AND it modifies your environment within the vim process, so just be aware of that.

Passing and using arguments to command in Vim script

How can I use functions in a user defined command? As a simple specific example:
How would I write a command that echos the argument passed to it?
I have this:
command -nargs=1 FW execute ":echo ".<args>
But when I run:
:FW something
I get:
E121: Undefined variable: something
E15: Invalid expression: ":echo".something
Because :echo takes an expression, a string must be quoted. This
is so common that Vim has a special notation for it; See :help <q-args>. Now, for
:execute, you'd need another level of quoting (and based on your comments it seems you went down that road):
:command! -nargs=1 FW execute "echo" string(<q-args>)
Also, you don't need to concatenate explicitly with .; the :execute command does that implicitly, and you can leave off the :.
But this double-quoting isn't necessary; you can skip the :execute:
:command! -nargs=1 FW echo <q-args>
You don't need a colon when you pass commands to "execute", it executes as if you were already in command mode.
You also don't need to concatenate strings with "." with execute if you want spaces between them, by default it concatenates multiple arguments with spaces.
I tried escaping args so that it would be concatenated as a string, this seems to work:
command -nargs=1 FW execute "echo" '<args>'
Is this what you were trying to achieve?
:h execute and :h user-commands are good reading.
edit:
some tests on this:
:FW "test"
test
:FW &shellslash
1
:FW 45
45
:FW "2+2"
"2+2"
:FW 2+2
4
As always, "execute" will execute anything you pass to it, so be careful.

Efficient way to refactor a class/method/string within a directory using vim

So far, I have been manually refactoring code by using the find-and-replace operation
%s:/stringiwanttoreplace/newstring/g
in vim.
But this is a slow and laborious process if I have stringiwanttoreplace in many files inside a specific directory.
My current/typical slow and laborious process involves a grep:-
grep -rn "stringiwanttoreplace" .
in my terminal to reveal all the locations/filenames where stringiwanttoreplace are; and now that I know which files contain stringiwanttoreplace, I will open each file one-by-one to perform the find-and-replace operation in each file.
Is there a more efficient workflow (in vim) to get this done?
CLARIFICATION: I would prefer a vim-based solution instead of a bash script/one-liner.
Here's the full sequence of commands that I would use:
/stringiwanttoreplace
:vimgrep /<c-r>// **
:Qargs
:argdo %s//newstring/g
:argdo update
In the first line, we search for the target pattern. That populates the last search pattern register (:help quote/), which means that we won't have to type it out in full again.
The :vimgrep command searches the entire project for the specified pattern. Type <c-r>/ as ctlr+r followed by / - this inserts the contents of the last search pattern register onto the command line. The first and last / symbols are delimiters for the search field. The trailing ** tells Vim to look inside every file and directory below the current directory.
At this point, the quickfix list will be populated with search matches from all matching files. :Qargs is a custom command, which populates the argument list with all of the files listed in the quickfix list. Here's the implementation:
command! -nargs=0 -bar Qargs execute 'args ' . QuickfixFilenames()
function! QuickfixFilenames()
" Building a hash ensures we get each buffer only once
let buffer_numbers = {}
for quickfix_item in getqflist()
let buffer_numbers[quickfix_item['bufnr']] = bufname(quickfix_item['bufnr'])
endfor
return join(values(buffer_numbers))
endfunction
Add that to your vimrc file.
Having run :Qargs, our argument list should now contain all of the files that include our target string. So we can run the substitution command with :argdo, to execute the command in each file. We can leave the search field of the substitution command blank, and it will automatically use the most recent search pattern. If you want, you could include the c flag when you run the substitution command, then you'll be prompted for confirmation.
Finally, the :argdo update command saves each file that was changed.
As #Peter Rincker pointed out, you should ensure that Vim's 'hidden' option is enabled, otherwise it will raise an error when you try to switch to another buffer before writing any changes to the active buffer.
Also, note that the last 3 commands can be executed in a single command line, by separating them with a pipe character.
:Qargs | argdo %s//replacement/gc | update
The :Qargs command is pinched from this answer (by me), which in turn was inspired by this answer by DrAl. A very similar solution was posted by #ib, which suggests to me that Vim should really implement something like :quickfixdo natively.
If you really want to do it in Vim you can follow the suggestions here.
You can call this from within Vim (:!find ...) but you don't need to:
find . -type f | xargs sed -i 's/stringiwanttoreplace/newstring/g'
Fine-tune the file selection with the dozens of parameters described in
man find
(e.g., replace only in HTML files: -name \*.html)
This solution will try to attempt the replacement in all files. You can filter that through grep before, but that is just doing twice the work for no gain.
By the way: sed uses almost the same syntax for regular expressions as Vim (stemming from the same history).
You could open all the files and type
:bufdo :s/stringiwanttoreplace/newstring/g
It performs the search/replace in all your buffers.
You don't need vim to do this, you can use command line tools. Using sed in a loop on the list of files to do this for you automatically. Something like this:
for each in `grep -l "stringiwanttoreplace" *` ;
do
cat $each | sed -e "s/stringiwanttoreplace/newstring/g" > $each
; done
vim7 has recursive grep built-in
:vimgrep /pattern/[j][g] file file1 file2 ... fileN
the result will be shown in a quickfix-window (:help quickfix)
to do the search recursively use the **-wildcard like
**/*.c to search through the current folder and recursively through all subdirectories.

How can I save each match in a different file with Vim

I would like to save the output of g/pattern1/,/pattern2/ to a file (for each match, a different file).
e.g.
def
.......
end
def
.......
end
you would end up with a file for each "def...end".
Tried using tempname() like so:
g/pattern1/,/pattern2/exe 'w ' . tempname() but this fails with no range allowed for exe
also tried
g/pattern1/,/pattern2/w `tempname()`
to get tempname() evaluated but this failed with a "too many filenames" error.
What am I missing? Can this be done by using global and other commands, or would you need vimscript to do it?
g/pattern1/,/pattern2/execute "w ".fnameescape(tempname())<CR>
Use execute whenever you want to insert variable into command-line if it is a mapping. If it is not, try using
g/pattern1/,/pattern2/w <C-r>=fn<Tab>e<Tab>te<Tab>)<CR><CR>
Here fn<Tab> with wildmode=longest,list:full will expand to fname, fnamee<Tab> will expand to fnameescape(, te<Tab> will expand to tempname(), so this is a short way to input <C-r>=fnameescape(tempname())<CR>. You can omit fnameescape if you are sure that tempname will not return filename with special characters.
And note that backticks will not execute vimscript function, they execute shell command, so `tempname()` tries to call tempname() in a shell and substitute filename with the result of this call. According to the help, you should have written `=tempname()`.
Try :g/pattern1/normal! :.,/pattern2/w `tempname()`^M with ^M entered as CTRL-V then ENTER

Resources