I have been trying to implement a function in vimscript to switch between two views of the same buffer. The functionality I want to have is like this:
I press a key and the screen position and cursor are moved to my previously saved location. If there is not yet another saved location, it creates one at the current view and cursor location. I have a vimscript function that does exactly this functionality i described:
fun! SwitchFileMarker(reset)
if a:reset == 1
if exists("b:switch_file_window")
unlet b:switch_file_window
endif
endif
if exists("b:switch_file_window")
let cursor_location = getpos("'0")
let top_location = getpos("'9")
:execute "normal! m0Hm9`0"
call setpos('.', top_location)
:execute "normal! zt"
call setpos('.', cursor_location)
:execute "normal! zv"
else
" save the location
:execute "normal! m0Hm9`0"
endif
let b:switch_file_window = 1
endfun
nmap <leader>b :call SwitchFileMarker(0)<CR>
nmap <leader>B :call SwitchFileMarker(1)<CR>
The only problem with this function is I want it to save the folds of the current view and load the folds of the saved view when the function is called. I can achieve this by using :mkview and :loadview, but the problem with that is if the number of lines in the file changes, the folds are lost. The :mkview function seems to remember the folds at a specific line number, and If ive added several lines above that fold location while editing the file, when i use :loadview, the fold is lost. using the marks, as done in the function i show works to save my cursor position (but not folds) because the marks keep track of the changing line number. The funcionality that I am trying to get is essentially like having two views of a buffer but in the same window, rather than two windows. If i have a two windows editing the same buffer, I can add lines in one window and the folds are not lost in the other, so this is exactly the functionality I want, just in one window instead of two. Any suggestions how this can be done?
Use marks!
To set a mark, you can use m<letter> or :mark <letter>.
To go to a marked line, use '<letter>. To go to the exact position, prefer `<letter>.
If the letter is lowercase, it is buffer-local. If it is uppercase, it is global.
Edit: OP wants to save folds, which this can’t do. But it’s helpful to know anyway, so I’m leaving the answer.
Related
There might be a simple solution for this. I couldn't find any solution to it (I might be searching with wrong context)
Here is my requirement.
I wrote the below key mapping in vimrc. It should print the line "Hello user_name." n times, where n and user_name are the user input once the key is pressed.
autocmd FileType ruby nnoremap <expr> <C-h> :call FuncPrnt(<-syntax to pass input from user->)
function! FuncPrnt(count, uname)
let c=a:count
let i=0
while i<c
call append(line("."), "Hello ".a:uname.".")
let i+=1
endwhile
endfunction
On Pressing the key user enters 3 and 'Ironman'. The output would be like
Hello Ironman.
Hello Ironman.
Hello Ironman.
Thanks in advance
A simpler approach is to just use a non-<expr> mapping to prepare the Ex command without executing it:
nnoremap <C-h> :call FuncPrnt(,"")<left><left><left><left>
If you associate that with a filetype (such as "ruby" in your case), make sure you create a local mapping using <buffer>. Otherwise the mapping will be global and will work on every buffer and not only those with Ruby source files.
If you use an autocmd in your vimrc, make sure you wrap it in an augroup to prevent getting duplicated commands if you reload your vimrc.
Alternatively, you can add filetype mappings for Ruby in a file ~/.vim/ftplugin/ruby.vim which gets automatically loaded by Vim whenever a file of type Ruby is loaded. (That way you don't need to use explicit autocmds, Vim will take care of those details on your behalf.)
Got the answer. Just in case someone else is searching for similar solution
autocmd FileType ruby nnoremap <expr> <C-h> input("", ":call FuncPrnt(,\"\")<left><left><left><left>")
will print the command and waits for the user to edit. Once the user edits and enters it is executed
Thinking outside of the box a little bit (questioning the premise), perhaps the best in this case is to define a user-defined command instead of a mapping.
You can define one here with:
command! -buffer -bar -count=1 -nargs=1 FuncPrnt
\ call FuncPrnt(<count>, <q-args>)
That way you can use it with:
:3FuncPrnt Ironman
If you omit the count, 1 will be used. (You can pick a different default as the argument to -count=N.)
You can use Tab completion for FuncPrnt, so perhaps :3Fu<Tab> or even :3F<Tab> might be enough to complete the command.
This might end up being quicker or more convenient to type than <C-H>3<right><right>, since it doesn't involve moving your hand to the arrow keys.
Just another vim source code comment question here. I have this mapping for my python source code files:
map <C-C> <Home>i#<Esc>
imap <C-C> <Home>#<Esc> i
On Ctrl-C it puts # in the beginning if the line to comment it out. This improves productivity a lot. But when I want to uncomment lines, I have to do this manually, meaning going to the first character of each commented line and remove it. This is very annoying. At the first glance, I can just bind Home-x to some key, but I can occasionally remove an innocent space or something else in case I misshit and do this on line that has no # character at the beginning of it. I first try to do some replacement with :%s// for a single line, but that has an unwanted affect - it triggers a search and highlights 'pattern' in other lines. In case of a single # character it is a fail.
Can anybody suggest how to remove a specified character in the beginning of current line in case it present and do nothing, if not, without using pattern replacement?
I have created a simple function to Toggle comment in the line:
function! ToggleComment()
let l:pos = col('.')
if getline('.') =~ '\v(\s+|\t+)?#'
exec 'normal! _"_x'
let l:pos -= 1
else
exec 'normal! I#'
let l:pos += 1
endif
call cursor(line("."), l:pos)
endfunction
nnoremap <Leader>t :call ToggleComment()<CR>
inoremap <Leader>t <C-o>:call ToggleComment()<CR>
I recommend Tim Pope's plugin vim-commentary because is way more complete. But of course our idea gives you guys a glimpse how far we can get with vimscript.
Another approach, which does not need to save windowview and toggles comments in other languages can be seen here
Can anybody suggest how to remove a specified character in the beginning of current line in case it present and do nothing, if not, without using pattern replacement?
A solution would be (assuming your cursor is anywhere to the right of # when using the map):
map <c-c> mmF#x`m
A more general solution would be to use a substitution and histdel() to delete the last search pattern:
function! DelComment()
s/^\( *\)#/\1/
call histdel("search", -1)
let #/ = histget("search", -1)
endfunction
After executing the function (by selecting it and typing :#") you can map it to <c-c>:
map <silent> <c-c> mm:silent! call DelComment()<cr>`m
I like using marks around functions to retain the cursor position after executing the map. Feel free to remove mm and `m in the above map.
I sometime dream of a mark-selection feature in Vim, just like when you type:
ma
in normal mode, you can then hit, from anywhere:
`a
to get back to this place a.
I wish there were something like:
ma
in visual mode. This would save your visually selected area. And you would just type then, from any place in normal mode:
<someHeader>a
to enter visual mode back with this a selection.
Is there such a feature in Vim? Or an extension that looks like? Or an easy way to implement it?
I'm fairly certain there isn't any way to name selections similar to how marks or registers can be referenced.
The closest feature that I can think of is the gv command which enters Visual mode with the last previously selected block already re-selected. I find this to be a lot more convenient than having to manually re-selecting the same block of text more than once.
It should be possible to write a function using Vimscript that saves the start and ends of a visual block as marks that can be re-used to reselect a Visual block. I had a look to see if it was possible to use Vimscript to save the < and > marks as other (less ephemeral) marks but I didn't see anything.
I have written this which I believe is what you are asking for:
function! VisualMark()
call inputsave()
let registers = input("m")
call inputrestore()
exec "normal! `<m" . registers[0]
exec "normal! `>m" . registers[1]
endfun
function! GetVisualMark()
call inputsave()
let registers = input("`")
call inputrestore()
exec "normal! `" . registers[0]
if strlen(registers) > 1
exec "normal! v`" . registers[1]
endif
endfun
vnoremap m <esc>:call VisualMark()<cr>
nnoremap ` :call GetVisualMark()<cr>
they should behave in the same way as a regular mark except only in visual mode, and they both need two arguments.
First, to get a visually selected area saved, in visual mode with the block selected, hit the m key like you normally would. It will then let you type in some more text... here the function will expect 2 characters which are the two registers that the beginning and end marks will be saved to... so for example entering mab in visual mode and then pressing enter (I could not figure out a way to make it work without an enter), would create a mark in register a for the beginning of the block, and b would be the end of the block.
To reselect the block visually, go to visual mode and do the ` which is the same as a regular mark... this too will expect two characters
EDIT:
I have made the implementation better in my opinion. Before I had it so that to bring back the visually selected block you had to be in visual mode then hit the backtick (same button that you would use for a normal mark) button and then the 2 registers. Now you do it in normal mode... This means that the default use of the backtick now uses this function, but the function now checks how many registers you provide... so if you only use one register, it will only take you back to that one. if you provide 2 registers, then it will visually select them... if you provide more than 2, it will only use the first 2.
The enter key is still required to enter in your selection though. It is probably better this way with the new implementation anyways.
For those that want to see, or use the old implementation this is it:
function! VisualMark()
call inputsave()
let registers = input("m")
call inputrestore()
exec "normal! `<m" . registers[0]
exec "normal! `>m" . registers[1]
endfun
function! GetVisualMark()
call inputsave()
let registers = input("`")
call inputrestore()
exec "normal! `" . registers[0]
exec "normal! v`" . registers[1]
endfun
vnoremap m <esc>:call VisualMark()<cr>
vnoremap ` <esc>:call GetVisualMark()<cr>
Here is the kind solution of Steven Hall, along with some modifications I needed for the visual marks not to overwrite regular registers.
https://github.com/iago-lito/vim-visualMarks
It is a small vimScript allowing one to mark a visually selected area by typing, in visual mode:
ma
(for mark a)
Then retrieve it later from any place by typing, in normal mode:
<a
It does still need some basic improvements, but now anyone can try, improve and share. Thank you Steven for having launched the process :)
I am trying to do a comment remap in Vim with an inline if to check if it's already commented or not. This is what I have already and of course it's not working ha ha:
imap <c-c> <Esc>^:if getline(".")[col(".")-1] == '/' i<Delete><Delete> else i// endif
What I want to do is check the first character if it's a / or not. If it's a / then delete the first two characters on that line, if it's not a / then add two // in front of the line.
What I had originally was this:
imap <c-c> <Esc>^i//
And that worked perfectly, but what I want is to be able to comment/uncomment at a whim.
I completely agree with #Peter Rincker's answer warning against doing this in insert mode, and pointing you to fully-featured plugins.
However, I couldn't resist writing this function to do precisely what you ask for. I find it easier to deal with this kind of mapping with functions. As an added bonus, it returns you to insert mode in the same position on the line as you started (which has been shifted by inserting or deleting the characters).
function! ToggleComment()
let pos=getpos(".")
let win=winsaveview()
if getline(".") =~ '\s*\/\/'
normal! ^2x
let pos[2]-=1
else
normal! ^i//
let pos[2]+=3
endif
call winrestview(win)
call setpos(".",pos)
startinsert
endfunction
inoremap <c-c> <Esc>:call ToggleComment()<CR>
Notice the modifications to pos to ensure the cursor is returned to the correct column. The command startinsert is useful in this type of function to return to insert mode. It is always safer to use noremap for mappings, unless there is a very good reason not to.
This seems to work well, but it is not very Vim-like, and you might find other plugins more flexible in the long run.
There are many commenting plugins for vim:
commentary.vim
tComment
EnhCommentify
NERD Commenter
and many more at www.vim.org
I would highly suggest you take a look at some these plugins first before you decide to roll your own. It will save you great effort.
As a side note you typically would want to comment/uncomment in normal mode not insert mode. This is not only the vim way, but will also provide a nicer undo history.
If you are dead set on creating your own mappings I suggest you create a function to do all the hard work and have your mapping call that function via :call. If you think you can get by with simple logic that doesn't need a function then you can use an expression mapping (see :h map-<expr>). You may want organize into a plugin as it could be large. If that is the case look at :h write-plugin to give you a feel
for writing plugins the proper way.
Example of a simple expression mapping for toggling comments:
nnoremap <expr> <leader>c getline(".") =~ '\m^\s*\/\/' ? '^"_2x' : 'I//<esc>`['
there's also this vimtip! http://vim.wikia.com/wiki/Comment/UnComment_visually_selected_text
i use the bottom one with the
...
noremap <silent> ,c :<C-B>sil <C-E>s/^/<C-R>=escape(b:comment_leader,'\/')<CR>/<CR>:noh<CR>
noremap <silent> ,u :<C-B>sil <C-E>s/^\V<C-R>=escape(b:comment_leader,'\/')<CR>//e<CR>:noh<CR>
,c comments out a region
,u uncomments a region
Okey, I agree - the title is useless. The thing is, I have no idea how to put this into a one liner...
What I'm trying to do is to map the space key so that it serves as a :nohl mapping, but at the same time, when it is on a folded line, to serve as a za in normal mode (open/close fold).
Is this even possible?
What I'm having trouble is distinguishing between the two - is there a way to "detect" a folded line below the cursor, or to detect an incremental search "currently in progress" (as in, there is something highlighted)?
Or am I tackling this in a completely wrong way? All advices welcomed!
Reusing ideas from Andy Rk:
function! FoldSetWithNoHL()
set nohls
if (foldclosed(line('.')) >= 0)
exe "normal za"
endif
endfunction
map <space> :silent! call FoldSetWithNoHL()<cr>
It may be ugly, but it worked for me:
noremap <Space> :nohl<CR> za
The drawback is an error occuring when pressing space on unfolded lines.
Incorporating fixes to the issues I listed in my earlier comment:
function! ToggleFoldWithNoHL()
if foldlevel('.')
normal! za
endif
endfunction
nnoremap <silent> <space> :nohlsearch<cr>:call ToggleFoldWithNoHL()<cr>
There doesn't seem to be a way to detect that there's an active search highlight, so if you have a search active inside a fold, this will clear the search but also close the fold. In that case, another space should put you right back where you want to be.