Is there a way to have vim not flag HTTP URLs and email addresses as spelling errors? Or, more generally, a way to list regexes of valid spellings?
3 Answers
You can add custom syntax rules, and give them the
@nospell
keyword,
this will tell Vim not to apply spell checking to this syntax match. For example:
:syn match UrlNoSpell "\w\+:\/\/[^[:space:]]\+" contains=@NoSpell
The above will work for text files, and some filetypes (such as markdown), but not for all filetypes.
Note that I used a fairly simple regex here; see How do I parse URL's from the text for some alternatives.
For other filetypes, you need to do a bit more work. For example, for python
files, comments are contained in a pythonComment
group, from /usr/share/vim/vim74/syntax/python.vim
:
syn match pythonComment "#.*$" contains=pythonTodo,@Spell
To override this, we need to do:
:syn match UrlNoSpellComment "\w\+:\/\/[^[:space:]]\+" contains=@NoSpell containedin=pythonComment
:highlight def link UrlNoSpellComment Comment
The trick is to add a list of previously matched syntax matches in which our
custom syntax match may be contained with containedin=
This tells Vim to look for the
UrlNoSpell
regex within the pythonComment
matches.
We also need to use highlight
to set the correct colours, since these aren't
inherited.
You need to do this in a number of places, for example for Python strings:
:syn match UrlNoSpellString "\w\+:\/\/[^[:space:]]\+" contains=@NoSpell containedin=pythonString
:highlight def link UrlNoSpellString String
We need 2 different syntax match groups so we can apply the correct syntax highlighting.
Of course, for other filestypes you need to use other containedin=
syntax
matches... There is, AFAIK, no "universal" solution, but looking up the correct
ones in /usr/share/vim/vim74/syntax/*.vim
should not be too difficult.
Note that all of the above commands must be executed after the syntax files; there are 2 ways to do this:
From a command or key mapping, this must be invoked manually every time. e.g.
fun! NoUrlSpell() if &filetype == 'python' :syn match UrlNoSpellComment "\w\+:\/\/[^[:space:]]\+" contains=@NoSpell containedin=pythonComment :highlight def link UrlNoSpellComment Comment :syn match UrlNoSpellString "\w\+:\/\/[^[:space:]]\+" contains=@NoSpell containedin=pythonString :highlight def link UrlNoSpellString String elseif &filetype == 'ruby' " ... else syn match pythonComment "#.*$" contains=pythonTodo,@Spell endif endfun command NoUrlSpell :call NoUrlSpell()
Put the commands in in the
~/.vim/after/syntax/[filetype].vim
. Vim will pick up these files and execute them after the default syntax files (see::help after-directory
).
-
“We also need to use
highlight
to set the correct colours, since these aren't inherited. […] We need 2 different syntax match groups so we can apply the correct syntax highlighting.” You can also mark the matchgroup as beingtransparent
. This way, the matched bits keep the style of their containing group and you don’t need a different rule for each containing group. However atransparent
matchgroup apparently isn’t matched at toplevel, so you’d still need two rules (one for toplevel, one for contained), but that is still simpler.– MaëlanCommented Jun 8, 2022 at 14:13 -
It looks like Cecil Curry's newer answer already covers that @Maëlan. I don't think I knew about
transparent
back in 2015. Commented Jun 8, 2022 at 16:55 -
Martin Tournoij's otherwise excellent answer fails to behave as expected for me – probably due to my leveraging diraol's phenomenal python-mode
plugin rather than Vim's default syntax file for Python.
To avoid highlighting URIs in Python comments, strings, or docstrings under python-mode
, add the following concise one-liner to your user-specific ~/.vim/after/syntax/python.vim
file:
syntax match NoSpellUriPython '\w\+:\/\/[^[:space:]]\+' transparent contained containedin=pythonComment,python.*String contains=@NoSpell
That's it. Crucially, note that this compresses what would have been twelve separate lines in Martin's answer into a single line. How? Elementary, my dear Vim-based Watson. We add:
- The
transparent
keyword, instructing Vim to inherit the highlight properties for this child syntax from its parent syntax (e.g., comments, strings). This enables us to avoid explicitly restatinghighlight def link
for each child syntax group. - The
contained
keyword, preventing this child syntax from extending past the boundaries of its parent syntax (e.g., EOL for comments, string delimiters for strings). - All parent syntax groups delimited by commas to the
containedin
keyword. The.*
regex operator enables us to cleverly match all Python string syntax groups (i.e.,pythonString
,pythonUniString
,pythonRawString
,pythonUniRawString
,pythonDocstring
) with minimal pain and maximal forward compatibility.
Although technically valid, the vimscript embedded in Martin's answer violates the DRY (Don't Repeat Yourself) principle. See also this similar answer for further details.
But wait... there's more.
Unasked-for Improvements for Great Glory
I'm sufficiently annoyed by both Vim's overly naïve default spell checking and third-party plugins (e.g., Spelunker, which unconditionally spell checks the entire buffer rather than merely code comments and strings) that I've decided... to actually do something about that. </gasp>
The well-tested Vim snippet below intelligently avoids spell checking all of the following within Python comments and strings:
- URIs (as above).
CamelCase
identifiers.snake_case
identifiers.UPPERCASE
identifiers.@
-prefixed identifiers (e.g.,@muhdecorator
)."
-delimited filetyped filenames (e.g.,"muh_module.py"
).:
-delimited substrings (e.g., the:func:
in :func:`re.sub`).- `-delimited substrings (e.g., the
re.sub
in :func:`re.sub`).
Add some or all of the following to your user-specific ~/.vim/after/syntax/python.vim
file and gasp in delight as Vim actually spell checks the RightStuff™ for once:
" Avoid spell checking URIs.
syntax match NoSpellPythonUri /\v\w+:\/\/[^[:space:]]+/ transparent contained containedin=pythonComment,python.*String contains=@NoSpell
" Avoid spell checking both CamelCase-formatted identifiers and uppercase
" identifiers. Since most languages (excluding Raku) prohibit Unicode in
" identifiers, these matches are intentionally confined to ASCII codepoints
" (e.g., "[A-Z]" rather than "[[:upper:]]").
syntax match NoSpellPythonCaps /\v<[A-Z]([A-Z0-9]{-1,}|[a-z0-9]+[A-Z0-9].{-})>/ transparent contained containedin=pythonComment,python.*String contains=@NoSpell
"FIXME: For unknown reasons, enabling this and *ONLY* this "syntax" statement
"causes subtle (but horrible) failures across "python-mode" indentation and
"syntax highlighting. While lamentable, we need "python-mode" more than we need
"to avoid spell checking snake_case-formatted identifiers. See also this
"currently unresolved upstream issue:
" https://github.com/python-mode/python-mode/issues/1083
"
" " Avoid spell checking snake_case-formatted identifiers.
" syntax match NoSpellPythonSnake
" \ /\v<\w+_.{-1,}>/ transparent
" \ contained containedin=pythonComment,python.*String contains=@NoSpell
" Avoid spell checking "@"-prefixed identifiers.
syntax match NoSpellPythonDecorator /\v\@[a-zA-Z].{-}>/ transparent contained containedin=pythonComment,python.*String contains=@NoSpell
" Avoid spell checking ":"-delimited substrings.
syntax match NoSpellPythonColons /\v:[^:]+:/ transparent contained containedin=pythonComment,python.*String contains=@NoSpell
" Avoid spell checking "`"-delimited substrings.
syntax match NoSpellPythonTicks /\v`[^`]+`/ transparent contained containedin=pythonComment,python.*String contains=@NoSpell
" Avoid spell checking '"'-delimited filetyped filenames matched as a
" double-quoted substring containing a filename prefix, a period, and one to
" four characters comprising a filetype.
syntax match NoSpellPythonPath /\v"[^"]+.[^"]{1,4}"/ transparent contained containedin=pythonComment,python.*String contains=@NoSpell
Of course, all of the above could (and possibly should) be reduced to a single one-liner matching one Godzilla-like regular expression that no one including myself would ever be able to maintain or even read. For everyone's sanity, I didn't do that.
If someone who is not me would like to make a GitHub-hosted Vim plugin expanding the above to other popular languages, that'd be just great. Vim's default spell checking implementation is almost there, folks; it just needs a helping hand from the open-source community.
Until then, may the StackOverflow be with you always!
-
3I kinda figured out this whole Vim syntax highlighting thing while writing answers here. I didn't always know what I was doing to be honest, so especially some of the older answers may be less-than-optimal 😅 Commented Apr 11, 2020 at 9:13
-
You're too modest, Martin. Without your "breaking the ice" answer, I and many others wouldn't have even known where to begin. Thanks for getting this editor party started! Rome may not have been built in a day, but at least we can spell check it in one now. Commented Apr 14, 2020 at 3:43
Elaborating on Martin and Cecil’s excellent answers, I came up with this.
All the code I give is to be put in your vimrc, after :syntax enable
(so that you can plug onto the syntax linter by doing :au Syntax * ...
).
So, the annoying part in the problem at hand is that you need to specify for all filetypes you care about, all the matchgroups in which you want your spelling exception to apply. There is no way around that, but you can relieve the task a bit by defining a cluster. I use this one.
au Syntax * syn cluster AllSpell contains=
\cComment,cCommentL,cString,cCppString,
\javaComment,javaLineComment,javaCommentTitle,javaString,
\javascriptComment,javascriptLineComment,jsonString,
\scalaMultilineComment,scalaTrailingComment,
\hsLineComment,hsBlockComment,
\ocamlComment,ocamlString,opamComment,opamString,
\lispComment,lispCommentRegion,lispString,
\makeComment,
\shComment,shSingleQuote,shDoubleQuote,zshComment,
\vimComment,vim9Comment,vimLineComment,vim9LineComment,vimMtchComment,vimString,
\pythonComment,pythonString,pythonRawString,
\luaComment,luaString,luaString2,
\perlComment,perlPOD,
\prologComment,prologCComment,prologString,
\texComment,bibField,bibComment,bibComment2,bibComment3,bibQuote,bibBrace,bibParen,
\xmlComment,xmlString,htmlComment,cssComment,
To figure out the matchgroups you want to insert into, you need to look into the code of the linters for the filetypes you care about (e.g. in /usr/share/vim/vimXX/yourfiletype.vim
). Grepping for Spell
helps. Typically that will be the matchgroups for comments and string literals.
According to Cecil’s answer you can use wildcards in matchgroup names, so for instance I could have shortened Edited 2022-06-16: Wildcards are expanded early so this will likely not work, because the matchgroups are not defined yet.vimComment,vim9Comment,vimLineComment,vim9LineComment,vimMtchComment
to just vim.*Comment
. Perhaps catching all of .*Comment,.*String
would be a good idea, if brutal? Still, not all matchgroup names follow this pattern so you still need to do that work of looking into the linting code.
Then, you can do:
au Syntax * syn match Url
\ "\w\+:\/\/[:/?#[\]@!$&'()*+,;=0-9[:lower:][:upper:]_\-.~]\+"
\ contains=@NoSpell containedin=@AllSpell transparent
au Syntax * syn match Url
\ "\w\+:\/\/[:/?#[\]@!$&'()*+,;=0-9[:lower:][:upper:]_\-.~]\+"
\ contains=@NoSpell
This uses a more satisfying regex for URLs (it matches a protocol://
followed by any succession of valid URL characters—as per RFC 3986—and Unicode letters).
The first rule matches contained URLs, thanks to the cluster just defined. It has the transparent
option so that it lets the containing matchgroup style the URL. Because of that option, the rule apparently doesn’t match URLs at toplevel, so a second rule is needed. (Of course, if you wanted to, you could also style the URL differently, e.g. :hi def link Url Underlined
; then you must remove the transparent
option and only one rule is needed.)
We can avoid repeating ourselves with a function:
fun! MatchNoSpell(group, regex)
let l:cmd = 'au Syntax * syn match ' . a:group . ' /'.a:regex.'/ contains=@NoSpell'
exe l:cmd . ' containedin=@AllSpell transparent'
exe l:cmd
endfun
call MatchNoSpell('Url',
\ '\w\+:\/\/[:/?#[\]@!$&''()*+,;=0-9[:lower:][:upper:]_\-.~]\+' )
This function is useful because it can be used for more spelling exceptions.