How do I define an exception for line breaks in Vim? - vim

I edit files in Vim were I log terminal command lines along with descriptions of what I did.
All my command lines start with $, so my files look like this:
This is a description of what this
command does, it can be quite long and
should have line breaks.
$ ./the_command.sh
These are actually Viki files, but I guess this question should apply to any file type. I have filetype detection on and the files are correctly identified.
The question now is:
I want (hard) line breaks to be inserted into all the text except for the actual copies of command lines, which could be easily identified by the leading $.
Is it possible in Vim to define an exception for applying the line break rule based on a pattern? Would I do that in the syntax file for viki files?
UPDATE
Using a combination of what Herbert and Jefromi suggested, I now have this in my .vimrc:
au CursorMovedI *.viki call SetTextWidth()
function! SetTextWidth()
if getline(".")=~'^\$'
set textwidth=1000
else
set textwidth=80
endif
endfunction
It does exactly what I want. Thanks guys!

I gather when you say you want "hard line breaks" you mean you want Vim to break a line automatically, as when it reaches a textwidth column. The best way to do this, I think, is to define an 'au' command that sets textwidth to a high number (higher than longest possible line) when it's on a line that begins with a "$".
So something like this would change textwidth whenever you enter or exit insert mode on a line:
au InsertEnter call SetTextWidth()
au InsertLeave call SetTextWidth()
function! SetTextWidth()
if getline(line('.')) =~ '^\$'
" [edit: 'set textwidth = 0' is preferable to line below]
set textwidth =1000
else
set textwidth=78
endif
endfunction
You might want to use the CursorMoved/CursorMovedI groups instead of InsertEnter/Leave since they're more fine-grained. They get triggered whenever you move the cursor, so the function ends up getting called lots more times, but function is simple enough that it's probably not going to introduce any noticeable degradation in performance.
For doing without a function at all you could probably use something like this:
au InsertEnter exec "set tw=" . getline(line('.'))=~'^\$' ? 1000 : 78
au InsertLeave exec "set tw=" . getline(line('.'))=~'^\$' ? 1000 : 78

I believe this would meet your criteria:
set textwidth=78
v/^$/normal gq/^$\|\%$^M
^M is ctrl-v followed by enter
Lets break this down into smaller peices
/^$\|\%$ is a pattern that matches every line not starting with a $. The \%$ will include the lines between the last $ started line and the end of file.
gq/^$\|\%$ formats from the current line up to the pattern /^$\|\%$
:normal {cmd} executes a normal mode commands on current line.
:v/pattern/ is equivalent to :g!/pattern/ which executes a command on every line lot matching /pattern/
This solution does not format as you type as #Herbert Sitz solution does. Instead formats the text in one fell swoop at the end.
You could of course apply this before each write with
au BufWritePre filename-pattern-here set textwidth=78 | v/^$/normal gq/^$\|\%$^M
au BufWritePost filename-pattern-here set textwidth=0

Although I'm not sure what you want (you should be a bit more specific on where (why, how) you want to insert hard line breaks), but you can issue a command like
:v:^\s*$: <Your command to insert line break, e.g. s/something/\r/ >
This above command searches for any line which does not start with any whitespace followed by a $, then executes the command you specify.
HTH

Related

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.

Fold only comments when file is opened in Vim

When I am viewing long code files with verbose comments in Vim, I would like to be able to load the files with comments folded but everything else unfolded. The current folding configuration I have in my .vimrc is:
set foldmethod=syntax
set nofoldenable
That way, when I want to start doing folds, I can just start executing z commands. But is there a way to only fold the (block) comments?
You could execute a global command to close all the block comment folds:
:g/^\/\*/foldc
This will execute :help foldclose on any line that starts with /* (a common block comment indicator). Notice the / and * need to be escaped in this instance. You don't need to escape the / if you use a different delimiter (e.g. :g#^/\*#foldc). If you want this to happen automatically you could add it in an autocommand. For example:
set fdm=syntax fen
augroup closeCommentFolds
au!
au FileType javascript %foldo | g/^\/\*/foldc
au FileType ruby %foldo | g/^=begin/foldc
augroup end
Note that in these examples the ^ character in regex specifies that the /* and =begin matches are at the beginning of the line. If you want to match if there's whitespace between the beginning of the line and the match then use ^\s* instead of ^. The %foldo will open all folds so that foldenable is set, but it won't fold everything (just comments in this case).

No more messing up whitespace in VIM

I have an BufWritePre hook added to my .vimrc that trims trailing whitespace before a buffer is saved. This is very convenient for me when editing my own code or that of others who also have a policy to always remove trailing whitespace. However, this makes me sometimes mess up the whitespace of others, which doesn't look very nice in version control.
I have two ideas how this could be solved in general, both of which I have specific problems with:
Option 1
After opening a file (maybe using a BufReadPost hook), detect whether there is trailing whitespace in the file. If yes, set a buffer-local flag to signal this. If the flag is set, disable the trimming before a save.
The problem I have with this approach is that I don't seem to figure out how I can detect whether there is trailing whitespace in the buffer. I know about =~, but how do I get the buffer contents as a string? Or even better, I can do a search using /\s+$<cr>, but how can I check if the search was successful (if there are hits)?
Option 2 (more intelligent)
It would be even better if the whitespace trimming would only happen on the lines that were actually modified. This way I could have the benefit of not having to care about trailing whitespace in my code but still not messing up the rest of the file. This raises the question: can I somehow get the line numbers of the lines I added or modified?
I'm new to Vimscript, so I'd appreciate any hints or tips :)
UPDATE: I settled with option 1 now:
" configure list facility
highlight SpecialKey term=standout ctermbg=yellow guibg=yellow
set listchars=tab:>-,trail:~
" determine whether the current file has trailing whitespace
function! SetWhitespaceMode()
let b:has_trailing_whitespace=!!search('\v\s+$', 'cwn')
if b:has_trailing_whitespace
" if yes, we want to enable list for this file
set list
else
set nolist
endif
endfunction
" trim trailing whitespace in the current file
function! RTrim()
%s/\v\s+$//e
noh
endfunction
" trim trailing whitespace in the given range
function! RTrimRange() range
exec a:firstline.",".a:lastline."substitute /\\v\\s+$//e"
endfunction
" after opening and saving files, check the whitespace mode
autocmd BufReadPost * call SetWhitespaceMode()
autocmd BufWritePost * call SetWhitespaceMode()
" on save, remove trailing whitespace if there was already trailing whitespace
" in the file before
autocmd BufWritePre * if !b:has_trailing_whitespace | call RTrim() | endif
" strip whitespace manually
nmap <silent> <leader>W :call RTrim()<cr>
vmap <silent> <leader>W :call RTrimRange()<cr>
Option 1 can benefit from search() function, like so:
let b:has_trailing_spaces=!!search('\v\s+$', 'cwn')
search() function returns a number of matched line (they start from 1) or 0 if nothing was found, !! turns it to either 1 or 0, dropping information about on which line search() found trailing whitespace. Without n flag search() moves the cursor which is, I guess, undesired. Without w it may search only in the part of buffer that is after the cursor (really depends on 'wrapscan' option).
Proposed option 2 implementation is a hack that uses InsertLeave and '[, '] markers:
augroup CleanInsertedTrailingSpaces
autocmd!
autocmd InsertLeave * let wv=winsaveview() | keepjumps lockmarks '[,']s/\s\+$//e | call winrestview(wv)
augroup END
It assumes that you only add trailing whitespaces after typing. It will break if you move your cursor across the lines in insert mode. You can also try adding
autocmd CursorHold * if getpos("'.")[1]!=0 | let wv=winsaveview() | keepjumps lockmarks '.s/\s\+$//e | call winrestview(wv) | endif
, this should remove trailing spaces at the line of last change (only one line, '[ and '] can’t be used here because they point to first and last lines to often to be useful). Both autocommands should add information to undo tree.
There is a second option for the option 2: git annotate is able to annotate current state of the file thus you can use grep to filter out lines that have both trailing spaces and uncommitted changes and use a hook to purge unwanted spaces from them before commit. Sad, but hg annotate is not able to do so and thus you will have to write something more complex, possibly in python. I can’t say anything about other VC systems.
I guess it would be better if you used set list listchars+=trail:- to see such spaces and thus be able to remove them manually if they accidentally appear (personally I can’t remember myself constantly adding trailing spaces by accident, though in comments and documentation they are used by me intentionally to indicate that paragraph continues). What do you do so that this problem appears?
I tend not to let vim automatically trim anything. As you say, this can be a nightmare if dealing with other peoples code, and can lead to unnecessary conflicts. The approach I take, to keep my own code tidy is to make whitespace visible. With vim this can be achieved by adding the following to your ~/.vimrc file:
highlight SpecialKey ctermfg=DarkGray
set listchars=tab:>-,trail:~
set list
The result is to show whitespace like this:
This allows me to keep files clean whilst I write them. Most other (GUI) editors have the ability to show whitespace too.
" Show trailing whitepace and spaces before a tab:
:highlight ExtraWhitespace ctermbg=red guibg=red
:match ExtraWhitespace /\s\+$\| \+\ze\t/
:autocmd ColorScheme * highlight ExtraWhitespace ctermbg=red guibg=red
This way any bad whitespace will glow in red. It's quite hard to miss.

Soft wrap at 80 characters in Vim in window of arbitrary width

I want to use Vim's soft wrap capability (:set wrap) to wrap some code at 80 characters, regardless of my actual window width.
I haven't been able to find a way to do this yet - all the soft wrapping seems tied to the width of the window
textwidth and wrapmargin are both for hard wrapping (they insert newline characters into the file)
vertical splitting into multiple windows and using :vertical resize 80 (possibly with :set breakat= to allow breaks on any character) on one of them sort of works (even though it's a bit hackish), but breaks when using :set number as the line numbers take up a variable number of columns (depending on the file length) and these are part of the 80.
Is there any way to do this in vim? It doesn't look promising, according to other sources.
Right now my approximation is just to have /^.\{80}\zs.\+ as my default search so it's at least highlighted. I thought about adding a :syntax item for it, but that broke when it overlapped other syntax items, so I dropped that idea.
You could
set a large minimum width for the line numbers column via :set numberwidth=6 and
then you could resize your window with :set columns=86 (or with the mouse) to the proper size.
If you edit a file with a million lines in it, you may have trouble, but that's unlikely. You're wasting 6 columns of screen real estate this way too. So there are still all kinds of problems.
You can highlight past the 80th column using :match like it says here and here.
Beyond that I can't see any way to do this. Seems like it'd be a nice feature though.
Try this:
set columns=80
autocmd VimResized * if (&columns > 80) | set columns=80 | endif
set wrap
set linebreak
set showbreak=+++
You can remove the if (&columns > 80) | if you always want 80 columns.
I don't have a solution to the soft wrap, but as for marking a column, as of Vim 7.3 (released 2010-08-15) :set colorcolumn=80 will highlight column 80. The color will depend on your syntax file.
See Vim 80 column layout concerns, :h colorcolumn.
Have you tried 'linebreak'?
*'linebreak'* *'lbr'* *'nolinebreak'* *'nolbr'*
'linebreak' 'lbr' boolean (default off)
local to window
{not in Vi}
{not available when compiled without the |+linebreak|
feature}
If on Vim will wrap long lines at a character in 'breakat' rather
than at the last character that fits on the screen. Unlike
'wrapmargin' and 'textwidth', this does not insert <EOL>s in the file,
it only affects the way the file is displayed, not its contents. The
value of 'showbreak' is used to put in front of wrapped lines.
This option is not used when the 'wrap' option is off or 'list' is on.
Note that <Tab> characters after an <EOL> are mostly not displayed
with the right amount of white space.
Combining eborisch's answer with some other answers I found here and things I had to work around, I came up with the following two-part solution:
This first part makes it easier to edit text with long lines:
" Allow enabling by running the command ":Freeform", or <leader>sw
command! Softwrap :call SetupSoftwrap()
map <Leader>sw :call SetupSoftwrap() <CR>
func! SetupFreeform()
" Use setlocal for all of these so they don't affect other buffers
" Enable line wrapping.
setlocal wrap
" Only break at words.
setlocal linebreak
" Turn on spellchecking
setlocal spell
" Make jk and 0$ work on visual lines.
nnoremap <buffer> j gj
nnoremap <buffer> k gk
nnoremap <buffer> 0 g0
nnoremap <buffer> $ g$
" Disable colorcolumn, in case you use it as a column-width indicator
" I use: let &colorcolumn = join(range(101, 300), ",")
" so this overrides that.
setlocal colorcolumn=
" cursorline and cursorcolumn don't work as well in wrap mode, so
" you may want to disable them. cursorline highlights the whole line,
" so if you write a whole paragraph on a single line, the whole
" paragraph will be highlighted. cursorcolumn only highlights the actual
" column number, not the visual line, so the highlighting will be broken
" up on wrapped lines.
setlocal nocursorline
setlocal nocursorcolumn
endfunc
With this alone you can get decent text wrapping for writing something like markdown, or a Readme.
As noted in other answers, getting wrapping at an exact column width requires telling vim exactly how many columns there are, and overwriting that each time vim gets resized:
command! -nargs=? Draft :call SetupDraftMode(<args>)
func! SetupDraftMode()
" I like 80 columns + 4 for line numbers
set columns=84
autocmd VimResized * if (&columns > 84) | set columns=84 | endif
:Softwrap
endfunc
There are still a couple of problems with this:
vim won't clear the screen outside of the columns you specify after calling set columns, and I can't figure out how to tell it to, so ideally you should call this immediately after opening vim
vim shows a prompt with the version number and some helpful commands when you open it, so these won't be cleared. You can add set shm+=I to disable that prompt
You can't open any vertical splits, because then both splits will be ~40 column. You would need to set columns to 2x your desired width and then always have a split open.
My vimscript is awful, but ideally someone could modify the Draft function above to take a column width as an argument, or use a global variable (g:explicit_vim_width?) that can be set manually if your window size changes.
There is no good way to do it. We can hack a makeshift setlocal softwrap with autocmd if we modify #eborisch answer. If we resize every time we enter a buffer, and we resize to a particular length when the local variable softwrap is set, we get the desired behaviour.
Let's suppose that we want to soft wrap to 80 columns, we can write the following in .vimrc.
augroup softwrap
autocmd VimResized * if (exists('b:softwrap') && &columns > 80) | set columns=80 | endif
autocmd BufEnter * set columns=999
augroup END
To turn on the mode for a particular buffer, use the following commands:
let b:softwrap=1
set columns=80

Resources