vimscript - edit another file from a custom command - vim

I'd like to create, move around and append to other_file.py after calling a custom command from within this_file.py.
So for instance, I'd like to write several lines of text to a range of lines in the other file.
Is there some way I can basically enter :edit mode in other_file.py from this function? This would allow me to move around, search and append to other_file as though I were in it.
Here's a run through of where I'm at:
I am in this_file.py, and I have called:
:MyComm other_file:line_1/line_2/line_3
This activates the following vimscript:
function MyFunc(param_string)
let param_split = split(a:param_string,":")
let file_name = param_split[0] . ".py"
let lines = split(class_split[1],"/")
call system("touch " . file_name)
" Here is where I want to loop through the lines and use them in other_file.py
endfunction
:command -nargs=1 MyComm :call MyFunc(<f-args>)

alternative A
You can continue going through the external commands, as you've started with touch. So, you could use sed or awk to replace / append lines to the other file. With Vim's system() command, you can pass stdin input, too. For example, if you want to copy lines from the current buffer, grab them with getline() and pass them to system(), either as a List or a String.
alternative B
However, I would recommend avoiding external commands, and do the text editing tasks inside Vim. For that you just need to :execute 'split' other_file, and do the edits just as you would interactively, using Ex commands or :normal. Note that there are some special Ex commands (like :wincmd w replacing <C-w>w normal mode commands) that can make this more readable.

Related

Selecting text and saving it with keymap

I've made c# command line program that takes a window handle and a string as parameters. It brings the window with the handle to the foreground, uses SendKeys to send the string that was passed, and brings the window that was previously at the foreground back to the foreground.
I made this program so that I could send selections of text from vim to any program I want, such as a python interactive.
I could not figure out how to send the selection directly to the program, but I did find out how to save selected text in visual mode(How can I save a text block in visual mode to a file in Vim? - It saves whole lines, but that is okay for my purposes. Ideally though, it would not save just lines). So I changed the styring paraeter of my program to a filename. My plan was that vim would save the selected text to a file, and call the program with the filename.
Here is part of my vimrc
let g:send_keys_handle = 0
let g:send_keys_file = $home . "\\vim\\send.txt"
function! SendSelection()
if g:send_keys_handle
silent !cls
let x = "silent !cmdHost " . g:send_keys_handle . " " . "\"" . g:send_keys_file . "\""
echom x
execute x
endif
endfunction
command! SendS call SendSelection()
noremap <F5> <esc>va(a(a(a(a(a(a(a(a(a(a(a(a(a(a(a(:w ~\vim\send.txt<cr>
The key mapping is not complete, but vim isn't even executing all of it now. When I press F5, it selects the topmost parentheses as expected, but it does not save the selection to a file.
The SendS command does work, so that is not the problem.
With all those repeated a( text objects, you presumably intend to capture the text in the outermost parentheses. Unfortunately, if there are less parens than text objects, Vim will beep and abort the mapping, so the remainder won't be executed.
A possible solution is to split this into two parts, and use :normal! for the selection. That will only abort the :normal part; the remainder of the mapping will still be executed. You need to re-establish the visual selection with gv, though.
nnoremap <F5> :execute 'normal! va(a(a(a(a(a(a(a(a(a(a(a(a(a(a(a('<CR>gv:w ~\vim\send.txt<CR>
An alternative to :write (which also emits messages, and sets the alternate file unless you prepend :keepalt) would be yanking of the selection, and use of the lower-level writefile() function to store the selection in a file.

Using the content of a register in a ex command

Buffer 0 contains the name of a file that I want to insert in the current vim buffer.
What I try doesn't work:
:r #0
In general, how can one use the content of a register in an ex script?
There are multiple ways:
When typing the command interactively, the <C-R> command inserts a register (or Vimscript expression via the = register): :r <C-r>0 There are many useful variants, see :help c_CTRL-R. You can also use this in a mapping, though I would prefer the alternative:
Interpolating the register contents via :execute, i.e. you're building the command as a string first: :execute 'read ' . #0

Is it possible to have Ctrl-K cut/uncut functionality in Vim like there is in Nano and Pico?

In case you're not familiar with how it works, in Pico and Nano you can hit ctrl-k multiple times and it will add each line to the clipboard. Then you can ctrl-u to "uncut" this. It's a very useful command. Vim does something similar with the dd command, but it only works one line at a time. Thus, you have to use visual mode to properly accomplish the above.
I couldn't find a good answer online so I rolled my own solution. You can add this to your vimrc file:
imap <C-k> <Esc>:execute #a ? 'normal! "Bdd' : 'normal! "bdd'<cr>:let #a=1<cr>:echo ''<cr>i
imap <C-u> <Esc>"bPi
autocmd CursorMovedI * execute(':let #a=0')
The register #a is used to track whether or not the cut line should be appended. The register #b is used as the clipboard register. Whenever the cursor position changes, you stop being in "append" mode. Thus, you can hit ctrl-k over and over to keep appending lines, but as soon as you move the cursor you go back to normal. I'm pretty sure this is how Nano and Pico implement it under the hood.
Is anyone aware of a cleaner solution?
Intro to Registers
The Vim commands you are looking for are delete/cut, dd and put/paste, p. Each of these commands by default use the unnamed register, "". So dd will delete a line and put the freshly deleted line into the unnamed register. p will put the contents from the unnamed register into your current buffer.
Vim has more than just the unnamed register, it also has named registers. These registers are a-z.
To see what your registers are set to you can execute :registers.
To use a named register prefix your command with quote and a lowercase letter, e.g. "add
Upper case letters will append instead of replace the contents of a register, e.g "Add"
The Vim Way
"add the first line, then append the next line via "Add. For repeated deletions use .
Use a text object. e.g. dap for delete a paragraph. See :h text-objects
Delete text via some motion. e.g. d} is delete till end of paragraph
Delete to a regex patter. e.g. d/foo<cr>
Lastly would be using visual mode, V6jd. Nothing wrong with using visual mode
Additionally you want to stay out of insert mode unless you are inserting text. You only want to be in insert move for short burst at a time. Most of the time you should be in Normal mode, hence the name.
For a nice post on the Vi/Vim way see this StackOverflow post: Your problem with Vim is that you don't grok vi.
Alternatives
If none of the standard Vim tricks satisfy your needs you may want to look into plugins that support line exchanging like Unimpaired or LineJuggler.
However if you really do want something similar this nano/pico features you can use the following by putting it in your ~/.vimrc file:
nnoremap Q :<c-u>call <SID>DeleteAppend()<cr>
function! s:DeleteAppend()
let save = #a
let #a = ##
let reg = get(g:, 'append_tick', -1) == b:changedtick ? 'A' : 'a'
execute 'normal! "' . reg . 'dd'
let g:append_tick = b:changedtick
let ## = #a
let #a = save
endfunction
The Q normal command will now delete a line and append additional deleted lines until a another command is executed. Example usage: QQjjQQQp
For more help please see
:h d
:h p
:h "
:h registers
:h :reg
:h motion
:h text-objects
:h .
:h visual-mode
Yes, there are many cleaner solutions:
12dd{motion}p " cut 12 lines, move elsewhere, paste
d5j{motion}p " cut from here to 5 lines down, move elsewhere, paste
d/foo<CR>{motion}p " cut from here to next 'foo', move elsewhere, paste
:.,23d<CR>:12put<CR> " cut from here to line 23, paste after line 12
:.,+5d<CR>:0put<CR> " cut from here to fifth line below, paste at top of buffer
or the truly amazing:
:.,11m35<CR> " move the lines between this one and 11 to after line 35
You can even do:
Vjjjjjjjjjjd{motion}p
One of the big advantages of Vim over other editors is its expressive language: don't think in "nano", think in "Vim".

VIM 7: How to navigate the code source tree from the root of the code base, in an efficient manner?

I have a system of navigating the code across multiple files in a huge code base, and I want to improve/fix a drawback that it currently has :
My shell is pre-configured to open at the root of my code base - lets call it Dev/.
During syncing/code building, I have a script which automatically stores the relative path of all .h and .c files in a single file to be used by cscope (lets call it cscope.files).
Once I sync, this file is updated - and then I can open any file I want in vim using the following command from Dev/ :
vif "part of file name",
where
vif: aliased to vi `grep !:1 cscope.files`
Provided I give a part of the filename long enough to uniquely identify it, I can immediately open it in vim.
Now, the drawback to this approach is, when I've already opened one file, and jump to another file without exiting vim, the only way I can do so is
:!vif *file2*
This spawns a new shell and then opens the file in a vim launched there. As a result, I can't switch between the two files (using Ctrl-^). I'm unable to come up with a solution that :
a) Lets me open any file from Dev/ instantly
b) Lets me open any other file inside vim (once I've opened an existing file) in the same shell, so that the 2 vim sessions are aware of each other (I can hop between the 2 using Ctrl-^)
I know this is a long question (how does one google this :) ), but I'm betting the solution is simple and obvious to someone more proficient in vim !!
Let me know if any part of the question is fuzzy, and I'll clarify it...
UPDATE:
I ultimately went the cscope way, after customizing using a shortcut (as using 'gf' on cscope.files still prevented me from toggling between 2 source files). See VIM 7 and cscope: Using "cscope find f" inside a keyboard mapping for switching between files for the shortcut.
Use vim's grep in something like this:
:map <F1> :vim <pattern> cscope.files<CR>gf
For example, with this:
vnoremap <F1> "ry:exe ':1vim /'.#r.'/ cscope.files'<CR>gf
you select (visual mode) the pattern you'd like to search for and then press F1. The first file that matches the pattern will open, replacing the current buffer*.
* If this is possible. i.e if current buffer is saved or if hidden is set etc.
If you prefer to get a prompt, use input():
nnoremap <F1> :exe ':1vim /'.input("Enter pattern: ").'/ cscope.files'<CR>
[ but then you have to manually gf because input() consumes the remaining characters of the map. To avoid this, you can use inputsave() and inputrestore() ]
update
... for example like this:
function! GetPat()
call inputsave()
let mypat = input("Enter pattern: ")
call inputrestore()
return mypat
endfunction
nnoremap <F1> :exe ':1vim /'.GetPat().'/ cscope.files'<CR>gf
I think that the Vim Fuzzy Finder plugin is well adapted to your use case.
As the name implies, using the plugin, you can find files using a fuzzy text search.
Additionnaly, it also works for other Vim ressources like buffers, tags, etc.
I don't use cscope myself, but it seems that you can use it to find files, see :help cscope-find.
Otherwise, something like (not tested) this could help:
"Custom function
function! MyFunc(pat)
" Get files list
let filelist = readfile('path/to/cscope.files')
" Filter non matching item out and see if only one item is left
if len(filter(filelist, 'v:var =~? '.a:pat)) == 1
" edit file
exec 'edit '.filelist[0]
else
" Report back
echom 'More than one match:'
for file in filelist
echom file
endfor
endif
endfunction
" Custom command
command! -bar -nargs=1 MyCom call MyFunc(<args>)
Also try using the built-in cscope integration:
:cs find f stdio.h
Cscope tag: stdio.h
# line filename / context / line
1 1 /usr/include/stdio.h <<<unknown>>>
2 1 /usr/include/bits/stdio.h <<<unknown>>>
Type number and <Enter> (empty cancels):
See :help cscope-suggestions for some mappings that may make it easier to use cscope from within vim.

Reading the result of an exe file in :vs in vim

Many of my programs are console like applications, which take a data file, and print out the results, either on screen (more often) or in another file.
Since these days I'm doing some analysis which requires a lot of little tempting with one input data file, just to get a few numbers, I usually go: edit data file, start program, get result, edit data file, start program, get result ...
So I was wondering, is there a way in vim, while having open the input file, to define a function which would open a vertical split and load the result of program (pro12.exe) in it ?
How would one go about that ?
The easiest thing to do is probably write a Makefile to run the program (and redirect output to a file), and :set the autoread option on in Vim. Then, you can create a macro r by typing qr:make<Enter>q and run the macro with #r. The macro will cause Vim to invoke make, which will run the program to update the data file. The autoread option will make sure that vim refreshes the updated file without prompting you first.
Assuming pro12.exe is in your %PATH%, this will invoke your helper app and vert-split-open a static output file name. Map to whatever key you like. QND: won't work when relative paths (bufnames) change via cd. YMMV
fun! RunPro12()
bufdo if bufname(bufnr($)) == '/path/to/pro12.exe.output' | close | endif
silent exe '!pro12.exe'
vs /path/to/pro12.exe.output
endfun
map <f3> :call RunPro12()<cr>
I don't like :bufdo solutions as this command always messes up the current windows organisation.
In normal time, I'd use lh#buffer#jump() that jumps to the window where the named buffer is opened if it is already opened, or opens the window otherwise.
function! s:Run0()
call lh#buffer#jump('/path/to/pro12.exe.output', 'vsp')
silent exe '!ls'
endfunction
Given the requested "to define a function which would open a vertical split", we could simply use:
function! s:Run1()
let bn = bufnr('/path/to/pro12.exe.output')
if bn >= 0
exe 'bw '.bn
endif
silent exe '!pro12'
vsp /path/to/pro12.exe.output
endfunction
nnoremap ยต :call <sid>Run1()<cr>
Last thing. If as I suspect pro12 writes to stdout (the standard output, i.e. ~ the console), the last two lines of the function shall become:
vsp /path/to/pro12.exe.output
r!pro12
The "/path/to/pro12.exe.output" may be replaced with anything unique. Typically, I'd use a scratch buffer with a unique name having "pro12 result" in it.
PS: if in the function (this is important) you add a
nnoremap <buffer> q :bw<cr>
you'll be able to simply quit the output window simply by hitting q, from within the output window.

Resources