How do I autocomplete and format brackets in Vim? - vim

I'm working my way through a Java book and as a result, I find myself typing in a lot of curly braces. Considering that I knew how powerful Vim can be, though being far from an expert, I've looked for ways to let it handle the braces for me. I came across the use of autocmds, and I put the following in my .vimrc:
autocmd FileType java inoremap <buffer> { {<CR><CR>}<Up><Tab>. I also tried just a normal inoremap, to no avail.
What I have now works for the highest level block. However, when I get to any block deeper, the <Tab> doesn't seem to execute. Everything else works as expected. But no matter how deep after that, but the cursor always ends up back at the beginning of the method level. I imagine that it's because I told it to add exactly one tab. How can I make the tab count depend on the depth?

Assuming you have some sort of autoindenting set up:
augroup Java
autocmd!
autocmd FileType java inoremap <buffer> { {<CR>}<C-c>O
augroup END
What your version does:
insert the opening brace,
insert a newline,
insert a newline,
insert the closing brace,
move the cursor up to column 1,
insert a tab.
You got it right: since you only insert a single tab this can only get you to the first level of indentation.
What my version does:
insert the opening brace,
insert a newline,
insert the closing brace,
exit insert mode without triggering autocommands,
open a new line above the current line.
:help O respects your autoindenting settings so it will enter insert mode at the right indentation level.
Alternatively, you could modify your version like this:
augroup Java
autocmd!
autocmd FileType java inoremap <buffer> { {<CR><CR>}<C-c>S
augroup END
Where you leave insert mode on the blank line between the braces and do :help S to enter insert mode at the right indentation level.
There are many ways to skin that cat.

I'd suggest letting Vim use its built-in Java mode. If you add
source $VIMRUNTIME/vimrc_example.vim
to your .vimrc, Vim will pick up a load of useful defaults, including automatic filetype detection and formatting rules. You'll find that opening a .java file will give you most of what you want.
And to anticipate your next question, if the tab width isn't what you want, add something like
autocmd FileType java setlocal shiftwidth=4
to the end of your .vimrc.

Related

Vim, when exiting without editing, creates unsaved buffer

I am having some weird behavior when it comes to VIM.
The behavior that I'm expecting:
When editing a file, if one is in insert mode and doesn't make any file modifications when you exit insert mode the buffer should be considered "saved".
The behavior that I'm actually getting:
When editing a file and when I exit insert mode while making NO MODIFICATIONS, the buffer says that the file has been edited. Even though
I tested if this behavior was normal in vim by opening a clean version of vim, and it seems that the behavior I'm expecting is the default.
This a snippet of my config, my full configuration is here: (https://github.com/TheBabu/Config/blob/master/.vimrc)
I'm assuming whatever is causing this behavior is here otherwise it might be a plugin
"Setup
set shell=bash
set nowrap
set number
set nocompatible
set noshowmode
set directory^=$HOME/.vim/.swapfiles
set undofile
set mouse=a
syntax on
colorscheme neodark
hi Normal ctermbg=none
sign define transparent_sign
augroup SignColFixAu
au!
au BufReadPost *.c,*.cc,*.h,*.cpp,*.hh,*.hpp,*.py,*.js,*.php,*.rs exe "sign place 1111 name=transparent_sign line=1 file=".#%
augroup end
inoremap <cr> <space><bs><cr>
inoremap <esc> ~<bs><esc>
inoremap <expr> <up> pumvisible() ? "\<c-p>" : "~\<bs>\<up>"
inoremap <expr> <down> pumvisible() ? "\<c-p>" : "~\<bs>\<down>"
inoremap <Esc>x <Esc>x
"Tabs
set listchars=tab:➡\
set list
set autoindent
set noexpandtab
set tabstop=4
set shiftwidth=4
..SNIP...
"Plugins
Plugin 'VundleVim/Vundle.vim'
Plugin 'scrooloose/nerdtree'
Plugin 'Valloric/YouCompleteMe'
Plugin 'vim-airline/vim-airline'
Plugin 'vim-airline/vim-airline-themes'
Plugin 'easymotion/vim-easymotion'
Plugin 'w0rp/ale'
Plugin 'godlygeek/tabular'
Plugin 'LukeLike/auto-pairs'
Plugin 'ananagame/vimsence'
Plugin 'preservim/nerdcommenter'
call vundle#end()
filetype plugin indent on
...SNIP...
Thank you for any help!
The problem is caused by your <Esc> mapping:
inoremap <esc> ~<bs><esc>
This mapping is inserting a ~ character then backspacing over it, which in practice doesn't change the contents, but still marks the buffer as modified. (Vim doesn't really track the exact contents of the buffer, but tracks whether any change was introduced, and both inserting ~ and backspacing over a character count as such.)
You mentioned in the comments you use this mapping (together with the <CR> mapping) to change how Vim behaves with indentation. The usual behavior of Vim is to remove auto-indentation when you move to a new line (with <CR> on an otherwise blank line) or leave Insert mode (with <Esc>.)
There doesn't seem to be a way to change this behavior in Vim. But the default Vim behavior is actually very useful, as it prevents ending up with lines with trailing spaces all over the place. (Many use Vim autocmd's to trim trailing whitespace on save and others use git hooks or code review bots to block source code files that have trailing whitespace.)
I imagine your motivation to keep the trailing whitespace is so that you can leave Insert mode and then later enter Insert mode again on that same line, while preserving indentation. But it turns out you don't really need to do that, since Vim has many ways to start Insert mode again with the proper indentation.
For example, if you insert a line above (with O) or below the current one (with o), Vim will insert the appropriate indentation on the new line. If you're on a blank line and want to start inserting there, then instead of using i to start Insert mode, use S to replace the contents of the current line. Since the line is empty, there won't be anything to replace, but the S command will start the replacement with the current indentation, so that should solve that too.
If you're already in Insert mode (and not necessarily at the beginning of a line), you can also use Ctrl+F to have Vim apply the proper indentation to the current line, so that's an option too. With some languages (notably Python), Vim can't always figure out the proper indentation (since you indent to end a block), so Vim might not be able to guess right. In those cases, you also have Ctrl+T to increase indentation and Ctrl+D to decrease it. Both are Insert mode commands and can be executed anywhere in the line.
Hopefully with the use of these commands you'll be able to let go of the trailing spaces that you're using to track indentation and also drop those mappings.
A good starting point to debug this might be comparing both original and modified versions after vim says its been edited.
To see the changes, use git diff if the file is VCS controlled, or vim -d original_copy edited_copy if it's not.
The diff-ed characters should give you an idea of the side-effect of the code that might be causing this. Paste the diff here if you can't figure that out.

Stop Vim from auto-inserting comments on newlines permanently? [duplicate]

While inserting a new line below a comment in vim, the result tends to insert a " at the start of the new line. It's probably a simple solution or reason why this is happening, but I am unable to find an exact solution.
If you’re editing a file of the vim filetype, Vim might by default insert the comment character (in Vimscript, this would be ") at the beginning of each new line you enter after a comment. As already mentioned, this is a result of Vim’s formatoptions setting.
To turn this behavior off in the current file, run
:set formatoptions-=ro
To turn it off by default, add this to your ~/.vimrc:
set formatoptions-=ro
To turn it off for Vimscript files, add this to your ~/.vimrc:
augroup filetype_vim
autocmd!
autocmd FileType vim setlocal formatoptions-=ro
augroup END
r and o are options which can be given to formatoptions. For the full list of possible options, run :help fo-table.
This behaviour is governed by the formatoptions variable.
Use :h formatoptions to find out more.
The following article might also be helpful: Disable automatic comment insertion.
I think this should work, regardless of your formatoptions settings.
inoremap <CR> <CR><C-U>
what command are you using to insert below?
If you use the standard "o" keystroke while in Navigation mode, it should insert a new line immediately below whatever the cursor is on, and automatically place you into Insert mode, without inserting an extra "
Similarly an uppercase "O" will insert a new line above whatever line the cursor is on, and place you in insert mode.

Run a command each time a file opens, or general syntax

I have a syntax rule that highlights trailing whitespace:
highlight Badspace ctermfg=red ctermbg=red
match Badspace /\s\+$/
This is in my .vimrc. It works fine, but the problem is I use splits a lot, and it seems that the match is only run on the first file you open, as well it should because the .vimrc should only run once.
Anyway, how can I get the above syntax to match any file that is opened? Is there a "general" syntax file? Is there any other way to run match each time a file opens rather than just once? I'd like to know both because I could end up using either one in the future.
The :match command applies the highlighting to a window, so you can use the WinEnter event to define an :autocmd.
:autocmd WinEnter * match Badspace /\s\+$/
Note that there are already a number of plugins for this purpose, most based on this VimTip: http://vim.wikia.com/wiki/Highlight_unwanted_spaces
They handle all that for you, and turn off the highlighting in insert mode; some can also automatically delete the whitespace. In fact, I have written a set of plugins for that, too: ShowTrailingWhitespace plugin.
You could accomplish this by using an autocmd:
highlight Badspace ctermfg=red ctermbg=red
autocmd BufEnter * match Badspace /\s\+$/
However, there's another way to accomplish your specific goal of marking trailing whitespace. Vim has a built-in feature for highlighting "special" whitespace, which includes tabs (to differentiate from spaces), trailing whitespace, and non-breaking spaces (character 160, which looks like a normal space but isn't).
See :help list and :help listchars. Here's what I use:
set list listchars=tab:>·,trail:·,nbsp:·,extends:>
listchars has the benefit of working with any file type, and marking up multiple whitespace types that are of interest. It is also a lot faster (match will be noticeably slow on giant files) and built-in already.
(Note that those are funky non-ASCII dot characters, which should work fine for you if you cut-and-paste into a UTF8-capable Vim. If they don't work for you, you can use any characters you like there, such as periods or underscores).
Here's what it looks like for me:
The correct approach to this problem is actually to use :syntax to define a custom syn-match.
Try putting this in your vimrc:
augroup BadWhitespace
au!
au Syntax * syn match customBadWhitespace /\s\+$/ containedin=ALL | hi link customBadWhitespace Error
augroup END
Edit: It should also be noted that there is built-in support for highlighting trailing whitespace with the 'list' option; see :help 'listchars' and :h hl-SpecialKey (SpecialKey is the highlight group used to highlight trailing whitespace characters when 'list' is on).
This is accomplished using autocmd. The events you're looking for are BufWinEnter and VimEnter. From the Vim manual:
BufWinEnter
After a buffer is displayed in a window. This
can be when the buffer is loaded (after
processing the modelines) or when a hidden
buffer is displayed in a window (and is no
longer hidden).
Does not happen for |:split| without
arguments, since you keep editing the same
buffer, or ":split" with a file that's already
open in a window, because it re-uses an
existing buffer. But it does happen for a
":split" with the name of the current buffer,
since it reloads that buffer.
VimEnter
After doing all the startup stuff, including
loading .vimrc files, executing the "-c cmd"
arguments, creating all windows and loading
the buffers in them.
Try putting this in your vimrc:
augroup BadWhitespace
au!
au VimEnter,BufWinEnter * match Badspace /\s\+$/
augroup END
Do :help autocmd for more info.
This is completely wrong because :match is window-local, not buffer-local. Ingo Karkat has the right idea. Unfortunately, there is no good way to avoid triggering the autocmd every time you enter the window.
More to the point, though, this is a job for a custom syntax, not match.

Get Vim to open new blocks like Sublime Text 2

In sublime text 2 when you:
BLOCK { <Return>
It generates (where the pipe is the cursor):
BLOCK {
|
}
How can I get Vim to behave this way?
I have autoindent on, and smartindent off because with smartindent it does this on return:
BLOCK {
|}
To be more clear, I'm specifically looking for 2 returns, moving up a line, and tabbing in (2 soft tabs to be specific). I already have it auto-matching characters like {, (, [ etc.
A simple mapping will work for most purposes:
imap {<cr> {<cr>}<c-o>O
Depending on plugins, some users may need inoremap instead of imap.
Before it was with TextMate, now it's with ST2.
You have basically two paths before you.
The "dumb" path
One could come up with dozens of variations of this method: you simply create a mapping that executes the series of key presses needed to reach your goal:
inoremap {<CR> {<cr><cr>}<C-o>k<tab>
I called it "dumb" but it doesn't mean that you would be dumb to use it: it's low-tech, has no dependencies, is easy to customize and it can be mapped to anything you like.
The "smart" method
This method involves the use of a plugin. I use DelimitMate but there are many others, choose your poison.
I did some quick digging for vim addons (which are often the solution to this sort of problem). I don't think I've found what you want: there are a few addons that come close, but nothing that inserts the extra newline before the closing brace.
You could do something like
imap { {<return><return>}<up><tab>
but this will get awkward if you are working in a language that uses braces in other situations. You could instead react to the newline:
inoremap <return> <return><return>}<up><tab>
Of course this will trigger on EVERY entered newline, rather than just those following an opening brace. To get it to check that the brace is the last character of the current line, you can:
Have a function (in ~/.vimrc or somewhere in ~/.vim/plugin) that looks like
function! CloseBraceIfOpened()
if getline(".")[-1:] == '{'
" insert a space and then delete it to preserve autoindent level
exec "normal o "
normal x
normal o}
normal k
else
normal o
endif
endfunction
also do
inoremap <buffer> <enter> <esc>:call CloseBraceIfOpened()<enter>A
Note that this imap is buffer-specific, so that mapping will only apply to the buffer you are in when you run it. To have it apply to all buffers, remove <buffer>.
If you are really ambitious/particular, you can do tests in the function to see if the code in the current line really opens a block.
To get the indentation working the way you want it, turn on the 'autoindent' and 'smartindent' settings.
: set autoindent smartindent
To have it on by default, add
set autoindent smartindent
to ~/.vimrc.
I use the following map:
inoremap {{ {<CR><CR>}<ESC>kcc
so instead of using {<CR> I use this mapping. Besides that I also use the plugin mentioned by romainl, DelimitMate for other mappings with braces.
I had the same problem and delimitMate solves it. After installing it you can enable it with:
let g:delimitMate_expand_cr = 1
There are lot's of hacks that delivers the SublimeText experience. Because I got frustrated I've created a project that includes all those features in a single vim distribution (without the need of installing/compiling external plugins/tools).
You can check it out from here: https://github.com/fatih/subvim

Pressing "Home" in Vim on an Indented Line

I have a bad habit of using the 'home' key to go back to the beginning of a line. As I recently started using vim I noticed that when I press the home key on a lined that is indented, it returns me to the very beginning of the line. In Notepad++ (the editor I used to use) it would return me to the beginning of the code on that line, right after the indent.
Is there some way to replicate this behavior in vim? Usually, when I'm pressing home it's in the Insert mode for me to (usually) stick a variable there.
I have set smartindent in my vimrc, with set noautoindent as a "tips" page told me to make sure to disable autoindent (although it didn't seem to be enabled in the first place - perhaps that option is extraneous.)
There are two usual ways to go to the "beginning" of a line in Vim:
0 (zero) go to the first column of text
^ go to the first non-whitespace on the line
I find that using 0w is usually the most convenient way for me to go to the first nonblank character on a line, it's the same number of keys as ^ and is easier to reach. (Of course, if there are no leading spaces on the line, don't press w.)
You could remap Home to be the same as ^ (the docs say Home's default function is equivalent to the movement command 1|):
:map <Home> ^
:imap <Home> <Esc>^i
Which should make the insert mode mapping be equivalent to escaping out of insert mode, pressing ^ and then returning to insert mode. I don't know about the best method of mapping a motion command for use inside insert mode, so this may break something, but it seems to work.
As to your indentation settings, they shouldn't have an effect on movement controls, but I also think you probably would prefer to have them set differently. autoindent just keeps your current indentation for new lines (so if you place 4 spaces at the beginning of a line, after you press return your new line will also have 4 spaces placed in front of it). I don't know why you wouldn't want that, since it's pretty useful in pretty much any programming language, or even just freeform text. smartindent on the other hand implements a couple of hard-coded lightly C-ish indentation rules, like indenting after an opening {, and deindenting after a closing }, but doesn't automatically carry over indentation from previous lines. The docs recommend keeping autoindent on if you use smartindent.
However, smartindent is useless for languages that don't meet its hard-coded rules, or even actively harmful (like when it automatically removes indentation from any line starting with a '#', which it thinks is a preprocessor directive but is wrong for python programmers trying to write an indented comment).
So vim also includes a more advanced indentation mode, filetype indentation, which allows flexible indentation rules on a per-language/filetype basis and is the preferred indentation mode for most people (even for C-like languages). If you do use filetype indentation, it's best to turn off smartindent (otherwise it can interfere with the filetype indentation, like moving all comment lines to column 0 in python files).
Personally, I always have autoindent on, use filetype when available, and never use smartindent. My .vimrc includes:
set autoindent " doesn't interfere with filetype indents, and is useful for text
if has("autocmd")
" Enable file type detection and indentation
filetype plugin indent on
set nosmartindent
endif
I imagine there's something you could do to have smartindent turned on only when filetype indenting doesn't exist for a filetype, if you're editing that many different C-like languages with no filetype indentation available.
Here’s what I have in my .vimrc. This maps Home to move to the beginning of the
text if you are anywhere in the line, and column 0 if you are at the beginning
of the text.
function ExtendedHome()
let column = col('.')
normal! ^
if column == col('.')
normal! 0
endif
endfunction
noremap <silent> <Home> :call ExtendedHome()<CR>
inoremap <silent> <Home> <C-O>:call ExtendedHome()<CR>
Note: I am using a keyboard layout that maps Home to Alt Gr+A, that why I’m using this. If you have to leave the letter field of your keyboard to reach Home, you should probably go to normal mode instead.
You could also use _ in Normal mode to go to the first non-whitespace character of the current line. You can also use a count with this motion.
_ <underscore> [count] - 1 lines downward,
on the first non-blank character linewise.
Try pressing 0 (also see :help 0)
also, this might help:
:imap <C-Home> <esc>0a

Resources