Vim errorformat and spectral lint - vim

I am attempting to define an error format for the Spectral OpenAPI linter (https://github.com/stoplightio/spectral). The code I have is below, but what I'm seeing is that after I run :make the quickfix window populates with lines from Spectral, but I can't navigate to error spots using it. There are no errors in Vim, the quick fix window just doesn't do much.
The messages from Spectral look like this:
/path/to/sample.yaml:25:9 error oas3-schema "Property `think` is not expected to be here."
My current Vimscript looks like this:
function! OpenAPIDetect()
if getline(1) =~ 'openapi:'
let &l:makeprg='spectral lint "' . expand('%') . '" -f text'
setlocal errorformat=%f:%l:%c\ (information\\|warning\\|error)\ (\\w\\|-)\+\ %m
setlocal errorformat+=%-GOpenAPI\ 3.x\ detected
endif
endfunction
augroup filetypedetect
au BufRead,BufNewFile *.{yml,yaml} call OpenAPIDetect()
augroup END

The following should be enough:
setlocal errorformat=%f:%l:%c\ %t%.%\\{-}\ %m
%f:%l:%c matches the colon-separated filename, line, and column,
%t matches a single character, used to infer the type of the error (error if e, warning if w, info if i, hint is not supported),
%.%\\{-} skips over the rest of the "type" word,
%m matches the rest of the message.
Also, the right place for :help 'errorformat' and :help 'makeprg' would be a :help :compiler file:
" in a minimal compiler/spectral.vim
if exists("current_compiler")
finish
endif
let current_compiler = "spectral"
CompilerSet makeprg=spectral\ lint\ %\ -f\ text
CompilerSet errorformat=%f:%l:%c\ %t%.%\\{-}\ %m
and the right place for that OpenAPI detection logic would be a :help ftplugin:
" in a minimal after/ftplugin/yaml.vim
if getline(1) =~ 'openapi:'
compiler spectral
endif

Related

Using template files in Vim

I am trying to use a template for Ruby files, by adding this to my .vimrc:
function! RubyTemplate()
" Add pragma comment
call setline(1, '# frozen_string_literal: true')
" Add two empty lines
call append(1, repeat([''], 2))
" Place cursor on line number 3
call cursor(3, 0)
endfunction
autocmd BufNewFile *.rb :call RubyTemplate()
However, this doesn't work and when I open a new Ruby file, it's empty.
Everything works as expected if I issue an :e! afterwards. However, this doesn't work if I add e! to the function, so I have to manually fire it every time.
What am I doing wrong?
You can use a static template file instead of invoking a function.
For instance, you can create a template file for your ruby scripts in your vim directory as ~/.vim/skeletons/ruby.skel, with the desired contents.
1 # frozen_string_literal: true
2
3
Then in your vimrc you should add the following code:
" Skeleton for .rb files
augroup ruby
" Remove all existing autocommands in the group
au!
au BufNewFile *.rb 0r ~/.vim/skeletons/ruby.skel
augroup end
Noah Frederick has an elegant solution that allows us to insert snippets manually or automatically.
It uses ultisnips plugin and two files
" after/plugin/ultisnips_custom.vim
if !exists('g:did_UltiSnips_plugin')
finish
endif
augroup ultisnips_custom
autocmd!
autocmd BufNewFile * silent! call snippet#InsertSkeleton()
augroup END
and
" autoload/snippet.vim
function! s:try_insert(skel)
execute "normal! i_" . a:skel . "\<C-r>=UltiSnips#ExpandSnippet()\<CR>"
if g:ulti_expand_res == 0
silent! undo
endif
return g:ulti_expand_res
endfunction
function! snippet#InsertSkeleton() abort
let filename = expand('%')
" Abort on non-empty buffer or extant file
if !(line('$') == 1 && getline('$') == '') || filereadable(filename)
return
endif
call s:try_insert('skel')
endfunction
In my case, I have done some changes but now if I create an empty python file, for example, I end up with:
An important note: In my case, if vim or neovim is not detecting the filetype correctly, and it can be done with auto commands, your automatic snippet insertion will not work.

Vim command to comment out a selection of lines?

Say I have a bunch of lines:
#Override
public void draw(Graphics g) {
g.setColor(Color.MAGENTA);
g.fillRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
g.setColor(Color.BLACK);
g.drawRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
}
When I want to comment them out with // (i prefer line comments instead of block comments), what I do is:
Place the cursor infront of the # symbol
Ctrl-V: Switch to enter block-select mode
Select the column down to the } closing parenthesis using multiple hits of j
Shift-I: to enter block-insert
Type //
ESC to excit
Enter to finish the command
--> The lines are now commented out.
Is there an easier way where I don't need to do the block-select? I found I can use a substitution like :'<, '>s/^/\/\///g but this has two problems:
Its very clumsy and error prone to type (multiple forward and backward slashes need to be
escaped)
It places the comment symbols (//) at the beginning of
the line (position 0), not at the position where the first character
of that line was (so indentation is lost).
How can I insert // on the selected lines at the position of the first character of each line using Vi?
You can define a custom mapping or command for your :substitute.
However, there are several commenter plugins that do this very well, and those are generic (and often extensible) so that they work for any filetype:
NERD Commenter plugin
tComment plugin
commentary.vim plugin
I'd highly recommend to use one of those plugins instead of trying to reinvent a poor solution yourself.
I use Commentary as in the other answer, but a few thoughts:
<C-v>jjjjj could be <C-v>} or <C-v>/}<CR>
:substitute doesn’t have to use / as a separator: :'<,'>s-^-//
with a visual selection, you can also do :'<,'>normal! I//
How can I insert // on the selected lines at the position of the first character of each line using Vi?
Although, I'm agree with others and the dedicated plugin is a must have, but, as it is formulated in the OP, that's quite an easy task which can be implemented as one-liner:
vnoremap <silent>gc :call setline(".", printf("%*s" . &cms, indent("."), "", trim(getline("."))))<CR>
Now select some text, press "gc", and, voila, it works. To force // usage instead of the default /**/ set the following option for your buffer: setlocal cms=//\ %s. See :h 'cms'.
" I have a 'toggle comment function' that looks like
" Reference: https://stackoverflow.com/a/24652257/2571881
" these lines are needed for ToggleComment()
" Reference: https://stackoverflow.com/a/24652257/2571881
autocmd FileType c,cpp,java let b:comment_leader = '//'
autocmd FileType arduino let b:comment_leader = '//'
autocmd FileType sh,ruby,python let b:comment_leader = '#'
autocmd FileType zsh let b:comment_leader = '#'
autocmd FileType conf,fstab let b:comment_leader = '#'
autocmd FileType matlab,tex let b:comment_leader = '%'
autocmd FileType vim let b:comment_leader = '"'
function! ToggleComment()
if exists('b:comment_leader')
let l:pos = col('.')
let l:space = ( &ft =~ '\v(c|cpp|java|arduino)' ? '3' : '2' )
if getline('.') =~ '\v(\s*|\t*)' .b:comment_leader
let l:space -= ( getline('.') =~ '\v.*\zs' . b:comment_leader . '(\s+|\t+)#!' ? 1 : 0 )
execute 'silent s,\v^(\s*|\t*)\zs' .b:comment_leader.'[ ]?,,g'
let l:pos -= l:space
else
exec 'normal! 0i' .b:comment_leader .' '
let l:pos += l:space
endif
call cursor(line("."), l:pos)
else
echo 'no comment leader found for filetype'
end
endfunction
nnoremap <Leader>t :call ToggleComment()<CR>
inoremap <Leader>t <C-o>:call ToggleComment()<CR>
xnoremap <Leader>t :'<,'>call ToggleComment()<CR>
" vnoremap <Leader>t :call ToggleComment()<CR>
So, once you have this function on your ~/.vimrc you can do:
vip ...................... visual inner paragraph
<leader>t ................ in order to call the function
Make a macro with q, lets put it into the a buffer, so hit qa on a given line. Then press I// to jump to start of line, and comment it out. hit Esc and q and now your macro is done. This macro will comment out the current line.
The full command is qaI//Escq
Now visually select a bunch of lines with V, and type :norm!#a to run your a macro over those lines. This will comment out a bunch of lines.
Record another macro to do the opposite with qb^xx. This can be invoked by visually selecting the lines you want to uncomment and typing norm!#b
You can save these macros in your .vimrc and map the specific macro to a key combination if you want to "save" these commands.

vim not echoing message after make

Consider a the following minimal vimrc:
set nocompatible
filetype plugin indent on
let g:tex_flavor = 'latex'
function! CompileLatex()
let save_pwd = getcwd()
lcd %:p:h
let compiler = 'pdflatex '
let mainfile = expand('%:p:r')
let &l:makeprg = compiler . mainfile . '.tex'
echon "compiling latex file..."
silent make!
execute 'lcd ' . save_pwd
endfunction
function! EchoLatexMessage()
redraw
echo 'This message is not shown'
endfunction
augroup echo_message
au!
au QuickFixCmdPost make call EchoLatexMessage()
augroup END
And in a foo.tex file like:
\documentclass{article}
\begin{document}
Foo
\end{document}
run :call CompileLatex(). As seen in the GIF the message This message is not shown from function EchoLatexMessage() is not shown (on the other hand the message compiling latex file... is always on screen). Why is this happening? I expect the new message to be echoed once :make finishes.
This is because of the silent make! in your function. The :silent apparently not only applies to the :make itself, but also to the autocmds invoked by it (which sort of makes sense). If you want to silence the compilation output itself, but not the messages from the autocmd, you can prepend :unsilent to :echo in the EchoLatexMessage() function.

How to auto highlight the current word in vim?

For example, our text is:
hello world
abcd hello world
hello world
In eclipse, when your cursor is at some word, the word hello is auto highlight in the current file. When you type ww in normal mode, the cursor is at other word world will highlight in the current file, the hello is un-highlighted automatically. This feature is very convenient for users.
Does vim can do this with some plugin or else?
Something like this?
set updatetime=10
function! HighlightWordUnderCursor()
if getline(".")[col(".")-1] !~# '[[:punct:][:blank:]]'
exec 'match' 'Search' '/\V\<'.expand('<cword>').'\>/'
else
match none
endif
endfunction
autocmd! CursorHold,CursorHoldI * call HighlightWordUnderCursor()
This won't clobber the search register but will use the same highlighting as would normally be used. (If you want a different highlight color change Search to that highlight group.) A short update time is needed so that the CursorHold event it fired fairly often. It also won't highlight anything if the cursor is above punctuation or whitespace.
The iskeyword setting determines what is considered part of a word when expand('<cword>') is used.
Improving #FDinoff's amazing answer, with custom highlight - dark BG and underline, and disable on quickfix list, fugitive filetype and when on diff:
function! HighlightWordUnderCursor()
let disabled_ft = ["qf", "fugitive", "nerdtree", "gundo", "diff", "fzf", "floaterm"]
if &diff || &buftype == "terminal" || index(disabled_ft, &filetype) >= 0
return
endif
if getline(".")[col(".")-1] !~# '[[:punct:][:blank:]]'
hi MatchWord cterm=undercurl gui=undercurl guibg=#3b404a
exec 'match' 'MatchWord' '/\V\<'.expand('<cword>').'\>/'
else
match none
endif
endfunction
augroup MatchWord
autocmd!
autocmd! CursorHold,CursorHoldI * call HighlightWordUnderCursor()
augroup END
Yes there is a vim plugin for highlighting the occurances of a word automatically. This one is implemented exclusively for $variables and ->properties in .php files.
DEMO :
And here is the same one but adapted for Perl files.
DEMO :
May be you can modify it for your purpose.
There's a script on vim.wikia.com for doing exactly that. It waits until you've stopped moving the cursor and then highlights all instances of the current word. You can then use n and N to jump between them like you normally would with search results.
I'm copying it here in case the link goes down:
" Highlight all instances of word under cursor, when idle.
" Useful when studying strange source code.
" Type z/ to toggle highlighting on/off.
nnoremap z/ :if AutoHighlightToggle()<Bar>set hls<Bar>endif<CR>
function! AutoHighlightToggle()
let #/ = ''
if exists('#auto_highlight')
au! auto_highlight
augroup! auto_highlight
setl updatetime=4000
echo 'Highlight current word: off'
return 0
else
augroup auto_highlight
au!
au CursorHold * let #/ = '\V\<'.escape(expand('<cword>'), '\').'\>'
augroup end
setl updatetime=500
echo 'Highlight current word: ON'
return 1
endif
endfunction
As noted in a comment on that page, if you always want this feature on you can just call the function from your vimrc after defining it. That way you can use z/ (or whatever shortcut you assign it to) to turn it off again later.

How can I trim blank lines at the end of file in Vim?

Sometimes I accidentally leave blank lines at the end of the file I am editing.
How can I trim them on saving in Vim?
Update
Thanks guys, all solutions seem to work.
Unfortunately, they all reset current cursor position, so I wrote the following function.
function TrimEndLines()
let save_cursor = getpos(".")
silent! %s#\($\n\s*\)\+\%$##
call setpos('.', save_cursor)
endfunction
autocmd BufWritePre *.py call TrimEndLines()
This substitute command should do it:
:%s#\($\n\s*\)\+\%$##
Note that this removes all trailing lines that contain only whitespace. To remove only truly "empty" lines, remove the \s* from the above command.
EDIT
Explanation:
\( ..... Start a match group
$\n ... Match a new line (end-of-line character followed by a carriage return).
\s* ... Allow any amount of whitespace on this new line
\) ..... End the match group
\+ ..... Allow any number of occurrences of this group (one or more).
\%$ ... Match the end of the file
Thus the regex matches any number of adjacent lines containing only whitespace, terminated only by the end of the file. The substitute command then replaces the match with a null string.
1. An elegant solution can be based on the :vglobal command
(or, which is the same thing, on the :global with ! modifier):
:v/\_s*\S/d
This command executes :delete on every line that does not have
non-whitespace characters in it, as well as after it in the remaining
text to the end of buffer (see :help /\s, :help /\S, and :help /\_
to understand the pattern). Hence, the command removes the tailing
blank lines.
To delete the empty lines in a strict sense—as opposed to blank ones
containing only whitespace—change the pattern in that :vglobal
command as follows.
:v/\n*./d
2. On huge sparse files containing large blocks of consecutive
whitespace characters (starting from about hundreds of kilobytes of
whitespace) the above commands might have unacceptable performance.
If that is the case, the same elegant idea can be used to transform
that :vglobal commands into much faster (but perhaps less
elegantly-looking) :delete commands with pattern-defined ranges.
For blank lines:
:0;/^\%(\_s*\S\)\#!/,$d
For empty lines:
:0;/^\%(\n*.\)\#!/,$d
The essence of both commands is the same; namely, removing the lines
belonging to the specified ranges, which are defined according to the
following three steps:
Move the cursor to the first line of the buffer before interpreting
the rest of the range (0;—see :help :;). The difference between
0 and 1 line numbers is that the former allows a match at the
first line, when there is a search pattern used to define the ending
line of the range.
Search for a line where the pattern describing a non-tailing blank
line (\_s*\S or \n*.) does not match (negation is due to the
\#! atom—see :help /\#!). Set the starting line of the range to
that line.
Set the ending line of the range to the last line of the buffer
(,$—see :help :$).
3. To run any of the above commands on saving, trigger it using
an autocommand to be fired on the BufWrite event (or its synonym,
BufWritePre).
You can put this into your vimrc
au BufWritePre *.txt $put _ | $;?\(^\s*$\)\#!?+1,$d
(replace *.txt with whatever globbing pattern you want)
Detail:
BufWritePre is the event before writing a buffer to a file.
$put _ appends a blank line at file end (from the always-empty register)
| chains Ex commands
$;?\(^\s*$\)\#!? goes to end of file ($) then (;) looks up backwards (?…?) for the first line which is not entirely blank (\(^\s*$\)\#!), also see :help /\#! for negative assertions in vim searches.
×××+1,$ forms a range from line ×××+1 till the last line
d deletes the line range.
Inspired by solution from #Prince Goulash, add the following to your ~/.vimrc to remove trailing blank lines for every save for Ruby and Python files:
autocmd FileType ruby,python autocmd BufWritePre <buffer> :%s/\($\n\s*\)\+\%$//e
I found the previous answers using substitute caused trouble when operating on very large files and polluted my registers. Here's a function I came up with which performs better for me, and avoids polluting registers:
" Strip trailing empty newlines
function TrimTrailingLines()
let lastLine = line('$')
let lastNonblankLine = prevnonblank(lastLine)
if lastLine > 0 && lastNonblankLine != lastLine
silent! execute lastNonblankLine + 1 . ',$delete _'
endif
endfunction
autocmd BufWritePre <buffer> call TrimTrailingLines()
I have a separated function called Preserve which I can call from other functions,
It makes easy to use Preserve to do other stuff:
" remove consecutive blank lines
" see Preserve function definition
" another way to remove blank lines :g/^$/,/./-j
" Reference: https://stackoverflow.com/a/7496112/2571881
if !exists('*DelBlankLines')
fun! DelBlankLines() range
if !&binary && &filetype != 'diff'
call Preserve(':%s/\s\+$//e')
call Preserve(':%s/^\n\{2,}/\r/ge')
call Preserve(':%s/\v($\n\s*)+%$/\r/e')
endif
endfun
endif
In my case, I keep at least one blank line, but not more than one, at the end of the file.
" Utility function that save last search and cursor position
" http://technotales.wordpress.com/2010/03/31/preserve-a-vim-function-that-keeps-your-state/
" video from vimcasts.org: http://vimcasts.org/episodes/tidying-whitespace
" using 'execute' command doesn't overwrite the last search pattern, so I
" don't need to store and restore it.
" preserve function
if !exists('*Preserve')
function! Preserve(command)
try
let l:win_view = winsaveview()
"silent! keepjumps keeppatterns execute a:command
silent! execute 'keeppatterns keepjumps ' . a:command
finally
call winrestview(l:win_view)
endtry
endfunction
endif
Here's why I have a separated Preserve function:
command! -nargs=0 Reindent :call Preserve('exec "normal! gg=G"')
" join lines keeping cursor position
nnoremap J :call Preserve(':join')<CR>
nnoremap <Leader>J :call Preserve(':join!')<CR>
" Reloads vimrc after saving but keep cursor position
if !exists('*ReloadVimrcFunction')
function! ReloadVimrcFunction()
call Preserve(':source $MYVIMRC')
" hi Normal guibg=NONE ctermbg=NONE
windo redraw
echom "Reloaded init.vim"
endfunction
endif
noremap <silent> <Leader>v :drop $MYVIMRC<cr>
command! -nargs=0 ReloadVimrc :call ReloadVimrcFunction()
" Strip trailing whitespaces
command! Cls :call Preserve(':%s/\v\s+$//e')
And if by any chance you want to create an autocommand you must create a group
to avoid overloading autocommands:
augroup removetrailingspaces
au!
au! BufwritePre *.md,*.py,*.sh,*.zsh,*.txt :call Preserve(':%s/\v\s+$//e')
augroup END
This one is to change file headers (if you have any suggestions) feel free to interact:
" trying avoid searching history in this function
if !exists('*ChangeHeader')
fun! ChangeHeader() abort
if line('$')>=7
call Preserve(':1,7s/\v(Last (Change|Modified)|date):\s+\zs.*/\=strftime("%b %d, %Y - %H:%M")/ei')
endif
endfun
endif
" dos2unix ^M
if !exists('*Dos2unixFunction')
fun! Dos2unixFunction() abort
"call Preserve('%s/ $//ge')
call Preserve(":%s/\x0D$//e")
set ff=unix
set bomb
set encoding=utf-8
set fileencoding=utf-8
endfun
endif
com! Dos2Unix :call Dos2unixFunction()

Resources