runtime(hare): update for Hare 0.25.2

closes: #18222

Signed-off-by: Amelia Clarke <selene@perilune.dev>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Amelia Clarke
2025-09-08 15:30:41 -04:00
committed by Christian Brabandt
parent 6bb16d2cee
commit 6d68508e62
11 changed files with 860 additions and 411 deletions

View File

@ -3,7 +3,7 @@ vim9script
# Vim functions for file type detection # Vim functions for file type detection
# #
# Maintainer: The Vim Project <https://github.com/vim/vim> # Maintainer: The Vim Project <https://github.com/vim/vim>
# Last Change: 2025 Sep 04 # Last Change: 2025 Sep 08
# Former Maintainer: Bram Moolenaar <Bram@vim.org> # Former Maintainer: Bram Moolenaar <Bram@vim.org>
# These functions are moved here from runtime/filetype.vim to make startup # These functions are moved here from runtime/filetype.vim to make startup
@ -441,29 +441,29 @@ export def FTfs()
endif endif
enddef enddef
# Recursively search for Hare source files in a directory and any # Recursively searches for Hare source files within a directory, up to a given
# subdirectories, up to a given depth. # depth.
def IsHareModule(dir: string, depth: number): bool def IsHareModule(dir: string, depth: number): bool
if depth <= 0 if depth < 1
return !empty(glob(dir .. '/*.ha')) return false
elseif depth == 1
return !glob(dir .. '/*.ha')->empty()
endif endif
return reduce(sort(glob(dir .. '/*', true, true), # Check all files in the directory before recursing into subdirectories.
(a, b) => isdirectory(a) - isdirectory(b)), return glob(dir .. '/*', true, true)
(acc, n) => acc ->sort((a, b) => isdirectory(a) - isdirectory(b))
->reduce((acc, n) => acc
|| n =~ '\.ha$' || n =~ '\.ha$'
|| isdirectory(n) || isdirectory(n) && IsHareModule(n, depth - 1),
&& IsHareModule(n, depth - 1),
false) false)
enddef enddef
# Determine if a README file exists within a Hare module and should be given the # Determines whether a README file is inside a Hare module and should receive
# Haredoc filetype. # the 'haredoc' filetype.
export def FTharedoc() export def FTharedoc()
if exists('g:filetype_haredoc') if IsHareModule('<afile>:h', get(g:, 'filetype_haredoc', 1))
if IsHareModule('<afile>:h', get(g:, 'haredoc_search_depth', 1)) setf haredoc
setf haredoc
endif
endif endif
enddef enddef

View File

@ -1,26 +1,82 @@
" Vim autoload file. vim9script
" Language: Hare
" Maintainer: Amelia Clarke <selene@perilune.dev>
" Last Updated: 2024-05-10
" Upstream: https://git.sr.ht/~sircmpwn/hare.vim
" Attempt to find the directory for a given Hare module. # Helper functions for Hare.
function hare#FindModule(str) # Language: Hare
let path = substitute(trim(a:str, ':', 2), '::', '/', 'g') # Maintainer: Amelia Clarke <selene@perilune.dev>
let dir = finddir(path) # Last Updated: 2025 Sep 06
while !empty(path) && empty(dir) # Upstream: https://git.sr.ht/~sircmpwn/hare.vim
let path = substitute(path, '/\?\h\w*$', '', '')
let dir = finddir(path)
endwhile
return dir
endfunction
" Return the value of HAREPATH if it exists. Otherwise use a reasonable default. # Returns the value of HAREPATH, if it exists. Otherwise, returns a safe
function hare#GetPath() # default.
if empty($HAREPATH) export def GetPath(): string
return '/usr/src/hare/stdlib,/usr/src/hare/third-party' var path: list<string>
if !empty($HAREPATH)
path = split($HAREPATH, ':')
else
path = ParsePath()
if empty(path)
return '/usr/src/hare/stdlib,/usr/src/hare/third-party'
endif
endif endif
return substitute($HAREPATH, ':', ',', 'g') return mapnew(path, (_, n) => escape(n, ' ,;'))->join(',')
endfunction enddef
" vim: et sts=2 sw=2 ts=8 # Converts a module identifier into a path.
export def IncludeExpr(): string
var path = trim(v:fname, ':', 2)->substitute('::', '/', 'g')
# If the module cannot be found, it might be a member instead. Try removing
# the final component until a directory is found.
while !finddir(path)
const head = fnamemodify(path, ':h')
if head == '.'
break
endif
path = head
endwhile
return path
enddef
# Modifies quickfix or location list entries to refer to the correct paths after
# running :make or :lmake, respectively.
export def QuickFixPaths()
var GetList: func
var SetList: func
if expand('<amatch>') =~ '^l'
GetList = function('getloclist', [0])
SetList = function('setloclist', [0])
else
GetList = function('getqflist')
SetList = function('setqflist')
endif
final list = GetList({ items: 0 })
for n in list.items
if !empty(n.module)
n.filename = findfile(n.module)
endif
endfor
SetList([], 'r', list)
enddef
# Attempts to parse the directories in $HAREPATH from the output of `hare
# version -v`. Otherwise, returns an empty list.
def ParsePath(): list<string>
if !executable('hare')
return []
endif
silent const lines = systemlist('hare version -v')
const min = match(lines, '^HAREPATH') + 1
if min == 0
return []
endif
const max = match(lines, '^\S', min)
return (max < 0 ? slice(lines, min) : slice(lines, min, max))
->mapnew((_, n) => matchstr(n, '^\s*\zs.*'))
enddef
# vim: et sts=2 sw=2 ts=8 tw=80

View File

@ -1,29 +1,35 @@
" Vim compiler file. vim9script
" Compiler: Hare
" Maintainer: Amelia Clarke <selene@perilune.dev>
" Last Change: 2024-05-23
" Upstream: https://git.sr.ht/~sircmpwn/hare.vim
if exists('current_compiler') # Vim compiler file.
# Compiler: Hare
# Maintainer: Amelia Clarke <selene@perilune.dev>
# Last Change: 2025 Sep 06
# Upstream: https://git.sr.ht/~sircmpwn/hare.vim
if exists('g:current_compiler')
finish finish
endif endif
let current_compiler = 'hare'
let s:cpo_save = &cpo
set cpo&vim
if filereadable('Makefile') || filereadable('makefile') if filereadable('Makefile') || filereadable('makefile')
CompilerSet makeprg=make CompilerSet makeprg=make
else else
CompilerSet makeprg=hare\ build const makeprg = 'hare build '
.. get(b:, 'hare_makeprg_params', get(g:, 'hare_makeprg_params', '-q'))
execute 'CompilerSet makeprg=' .. escape(makeprg, ' "\|')
endif endif
CompilerSet errorformat= CompilerSet errorformat=
\%f:%l:%c:\ syntax\ error:\ %m, \%o:%l:%v:\ syntax\ error:\ %m,
\%f:%l:%c:\ error:\ %m, \%o:%l:%v:\ error:\ %m,
\Error:\ %m,
\%-G%.%# \%-G%.%#
let &cpo = s:cpo_save augroup HareQuickFix
unlet s:cpo_save autocmd!
autocmd QuickFixCmdPost make hare#QuickFixPaths()
autocmd QuickFixCmdPost lmake hare#QuickFixPaths()
augroup END
" vim: et sts=2 sw=2 ts=8 g:current_compiler = 'hare'
# vim: et sts=2 sw=2 ts=8 tw=80

View File

@ -1,77 +1,134 @@
*ft_hare.txt* Support for the Hare programming language *ft_hare.txt* Support for the Hare programming language
============================================================================== ==============================================================================
CONTENTS *hare* CONTENTS *hare* *hare.vim*
1. Introduction |hare-intro| 1. Introduction |ft-hare-intro|
2. Filetype plugin |hare-plugin| 2. Filetype plugin |ft-hare-plugin|
3. Settings |hare-settings| 3. Haredoc filetype |ft-haredoc-plugin|
4. Indentation settings |ft-hare-indent|
5. Compiler support |compiler-hare|
============================================================================== ==============================================================================
INTRODUCTION *hare-intro* INTRODUCTION *ft-hare-intro*
This plugin provides syntax highlighting, indentation, and other functionality This plugin provides syntax highlighting, indentation, and other supporting
for the Hare programming language. Support is also provided for README files functionality for the Hare programming language.
inside Hare modules, but this must be enabled by setting |g:filetype_haredoc|.
==============================================================================
FILETYPE PLUGIN *hare-plugin*
This plugin automatically sets the value of 'path' to include the contents of FILETYPE PLUGIN *ft-hare-plugin*
the HAREPATH environment variable, allowing commands such as |gf| to directly
open standard library or third-party modules. If HAREPATH is not set, it
defaults to the recommended paths for most Unix-like filesystems, namely
/usr/src/hare/stdlib and /usr/src/hare/third-party.
============================================================================== This plugin has a few different variables that can be defined inside your
SETTINGS *hare-settings* |vimrc| to tweak its behavior.
This plugin provides a small number of variables that you can define in your Additionally, support is provided for folding `{ }` blocks. To enable folding,
vimrc to configure its behavior. add the following to a file inside your |after-directory| (e.g.
~/.vim/after/ftplugin/hare.vim): >
*g:filetype_haredoc* setlocal foldmethod=syntax
This plugin is able to automatically detect Hare modules and set the "haredoc"
filetype for any README files. As the recursive directory search used as a Because block-based folding tends to create many small folds, consider setting
heuristic has a minor performance impact, this feature is disabled by default a few related options, such as 'foldminlines' and 'foldnestmax'.
and must be specifically opted into: >
let g:filetype_haredoc = 1
<
See |g:haredoc_search_depth| for ways to tweak the searching behavior.
*g:hare_recommended_style* *g:hare_recommended_style*
The following options are set by default, in accordance with the official Hare The following options are set by default, in accordance with Hare's official
style guide: > style guide: >
setlocal noexpandtab setlocal noexpandtab
setlocal shiftwidth=0 setlocal shiftwidth=0
setlocal softtabstop=0 setlocal softtabstop=0
setlocal tabstop=8 setlocal tabstop=8
setlocal textwidth=80 setlocal textwidth=80
<
To disable this behavior: > To disable this behavior, add the following to your |vimrc|: >
let g:hare_recommended_style = 0 let g:hare_recommended_style = 0
< <
*g:hare_space_error* *g:hare_symbol_operators*
By default, trailing whitespace and tabs preceded by space characters are By default, symbolic operators do not receive any special highlighting (with
highlighted as errors. This is automatically turned off when in insert mode. `!`, `?`, and `::` being the only exceptions). To enable syntax highlighting
To disable this highlighting completely: > for most other operators, add the following to your |vimrc|: >
let g:hare_space_error = 0
<
*g:haredoc_search_depth*
By default, when |g:filetype_haredoc| is enabled, only the current directory
and its immediate subdirectories are searched for Hare files. The maximum
search depth may be adjusted with: >
let g:haredoc_search_depth = 2
<
Value Effect~
0 Only search the current directory.
1 Search the current directory and immediate
subdirectories.
2 Search the current directory and two levels of
subdirectories.
The maximum search depth can be set to any integer, but using values higher let g:hare_symbol_operators = 1
than 2 is not recommended, and will likely provide no tangible benefit in most <
situations. *g:hare_space_error*
By default, trailing whitespace and spaces followed by <Tab> characters will
be highlighted as errors. This is automatically disabled in Insert mode. To
turn off this highlighting completely, add the following to your |vimrc|: >
let g:hare_space_error = 0
HAREDOC FILETYPE *ft-haredoc-plugin*
This plugin will automatically detect README files inside Hare modules, using
a recursive directory search, and give them the "haredoc" filetype. Because
this is such a common filename, this plugin only searches for Hare source
files within the same directory by default.
*g:filetype_haredoc*
The |g:filetype_haredoc| variable can be used to tweak the depth of this
search, or bypass the detection of Hare documentation files altogether:
Value Effect~
0 No automatic detection
1 Search current directory only (this is the default)
2 Search one level of subdirectories
3 Search two levels of subdirectories
The search depth may be any positive integer, but values higher than `2` are
unlikely to provide a tangible benefit in most situations.
INDENTATION SETTINGS *ft-hare-indent*
Unlike most other settings for this plugin, the indentation settings may also
be set per-buffer, overriding any global configuration that exists. To do
this, simply prefix the variable with |b:| instead of |g:|.
*g:hare_indent_match_switch*
By default, continuation lines for "match" and "switch" conditions are
indented only one level: >hare
const file = match (os::create(path, 0o644,
flag::WRONLY | flag::TRUNC)) {
case let file: io::file =>
yield file;
// ...
If you instead prefer indenting them two levels, to more closely resemble "if"
and "for" conditions, add the following line to your |vimrc|: >
let g:hare_indent_match_switch = 2
<
*g:hare_indent_case*
By default, continuation lines for cases in "match" and "switch" expressions
are indented two levels, to visually distinguish them from the body of the
case: >hare
case ltok::I8, ltok::I16, ltok::I32,
ltok::I64, ltok::INT =>
// ...
If you prefer a different amount of indentation, you can adjust it using
|g:hare_indent_case|. Valid values include `0`, `1`, and `2`.
COMPILER SUPPORT *compiler-hare*
If this plugin detects a Makefile in the current directory, it will assume you
wish to use `make` for your build system, and will leave 'makeprg' untouched.
Otherwise, `hare build` will be used.
*g:hare_makeprg_params*
When `hare build` is used, additional compiler options may be appended to
'makeprg' with the |g:hare_makeprg_params| variable. It may also be set on a
per-buffer basis (using |b:| instead of |g:|), overriding any global
configuration that exists. For example: >
let b:hare_makeprg_params = '-lc -t o'
The global default is "-q", to suppress writing to stdout while building.
============================================================================== ==============================================================================
vim:tw=78:ts=8:noet:ft=help:norl: vim:ft=help:noet:ts=8:tw=78:norl:

View File

@ -6718,6 +6718,7 @@ compiler-dotnet quickfix.txt /*compiler-dotnet*
compiler-gcc quickfix.txt /*compiler-gcc* compiler-gcc quickfix.txt /*compiler-gcc*
compiler-gnat ft_ada.txt /*compiler-gnat* compiler-gnat ft_ada.txt /*compiler-gnat*
compiler-groff quickfix.txt /*compiler-groff* compiler-groff quickfix.txt /*compiler-groff*
compiler-hare ft_hare.txt /*compiler-hare*
compiler-hpada ft_ada.txt /*compiler-hpada* compiler-hpada ft_ada.txt /*compiler-hpada*
compiler-javac quickfix.txt /*compiler-javac* compiler-javac quickfix.txt /*compiler-javac*
compiler-make quickfix.txt /*compiler-make* compiler-make quickfix.txt /*compiler-make*
@ -7508,6 +7509,10 @@ ft-gprof-plugin filetype.txt /*ft-gprof-plugin*
ft-groff-syntax syntax.txt /*ft-groff-syntax* ft-groff-syntax syntax.txt /*ft-groff-syntax*
ft-gsp-syntax syntax.txt /*ft-gsp-syntax* ft-gsp-syntax syntax.txt /*ft-gsp-syntax*
ft-hare filetype.txt /*ft-hare* ft-hare filetype.txt /*ft-hare*
ft-hare-indent ft_hare.txt /*ft-hare-indent*
ft-hare-intro ft_hare.txt /*ft-hare-intro*
ft-hare-plugin ft_hare.txt /*ft-hare-plugin*
ft-haredoc-plugin ft_hare.txt /*ft-haredoc-plugin*
ft-haskell-syntax syntax.txt /*ft-haskell-syntax* ft-haskell-syntax syntax.txt /*ft-haskell-syntax*
ft-help-omni helphelp.txt /*ft-help-omni* ft-help-omni helphelp.txt /*ft-help-omni*
ft-html-indent indent.txt /*ft-html-indent* ft-html-indent indent.txt /*ft-html-indent*
@ -7759,9 +7764,12 @@ g:gnat.Set_Project_File() ft_ada.txt /*g:gnat.Set_Project_File()*
g:gnat.Tags() ft_ada.txt /*g:gnat.Tags()* g:gnat.Tags() ft_ada.txt /*g:gnat.Tags()*
g:gnat.Tags_Command ft_ada.txt /*g:gnat.Tags_Command* g:gnat.Tags_Command ft_ada.txt /*g:gnat.Tags_Command*
g:gzip_exec pi_gzip.txt /*g:gzip_exec* g:gzip_exec pi_gzip.txt /*g:gzip_exec*
g:hare_indent_case ft_hare.txt /*g:hare_indent_case*
g:hare_indent_match_switch ft_hare.txt /*g:hare_indent_match_switch*
g:hare_makeprg_params ft_hare.txt /*g:hare_makeprg_params*
g:hare_recommended_style ft_hare.txt /*g:hare_recommended_style* g:hare_recommended_style ft_hare.txt /*g:hare_recommended_style*
g:hare_space_error ft_hare.txt /*g:hare_space_error* g:hare_space_error ft_hare.txt /*g:hare_space_error*
g:haredoc_search_depth ft_hare.txt /*g:haredoc_search_depth* g:hare_symbol_operators ft_hare.txt /*g:hare_symbol_operators*
g:help_example_languages helphelp.txt /*g:help_example_languages* g:help_example_languages helphelp.txt /*g:help_example_languages*
g:html_charset_override syntax.txt /*g:html_charset_override* g:html_charset_override syntax.txt /*g:html_charset_override*
g:html_diff_one_file syntax.txt /*g:html_diff_one_file* g:html_diff_one_file syntax.txt /*g:html_diff_one_file*
@ -8252,9 +8260,7 @@ haiku-vimdir os_haiku.txt /*haiku-vimdir*
hangul hangulin.txt /*hangul* hangul hangulin.txt /*hangul*
hangulin.txt hangulin.txt /*hangulin.txt* hangulin.txt hangulin.txt /*hangulin.txt*
hare ft_hare.txt /*hare* hare ft_hare.txt /*hare*
hare-intro ft_hare.txt /*hare-intro* hare.vim ft_hare.txt /*hare.vim*
hare-plugin ft_hare.txt /*hare-plugin*
hare-settings ft_hare.txt /*hare-settings*
has() builtin.txt /*has()* has() builtin.txt /*has()*
has-patch builtin.txt /*has-patch* has-patch builtin.txt /*has-patch*
has-python if_pyth.txt /*has-python* has-python if_pyth.txt /*has-python*

View File

@ -1,61 +1,52 @@
" Vim filetype plugin. vim9script
" Language: Hare
" Maintainer: Amelia Clarke <selene@perilune.dev> # Vim filetype plugin.
" Last Updated: 2024 Oct 04 # Language: Hare
" Upstream: https://git.sr.ht/~sircmpwn/hare.vim # Maintainer: Amelia Clarke <selene@perilune.dev>
# Last Updated: 2025 Sep 06
# Upstream: https://git.sr.ht/~sircmpwn/hare.vim
if exists('b:did_ftplugin') if exists('b:did_ftplugin')
finish finish
endif endif
let b:did_ftplugin = 1 b:did_ftplugin = 1
let s:cpo_save = &cpo # Use the Hare compiler.
set cpo&vim compiler hare
b:undo_ftplugin = 'compiler make'
" Formatting settings. # Formatting settings.
setlocal comments=:// setlocal comments=://
setlocal commentstring=//\ %s setlocal commentstring=//\ %s
setlocal formatlistpat=^\ \\?-\ setlocal formatlistpat=^\\s*-\
setlocal formatoptions+=croqnlj/ formatoptions-=t setlocal formatoptions+=croqnlj/ formatoptions-=t
b:undo_ftplugin ..= ' | setl cms< com< flp< fo<'
" Search for Hare modules. # Locate Hare modules.
setlocal include=^\\s*use\\> &l:include = '\v^\s*use\s+%(\h\w*\s*\=)?'
setlocal includeexpr=hare#FindModule(v:fname) setlocal includeexpr=hare#IncludeExpr()
setlocal isfname+=: setlocal isfname+=:
&l:path = ',,' .. hare#GetPath()
setlocal suffixesadd=.ha setlocal suffixesadd=.ha
b:undo_ftplugin ..= ' | setl inc< inex< isf< pa< sua<'
" Add HAREPATH to the default search paths. # Follow the official style guide by default.
setlocal path-=/usr/include,,
let &l:path .= ',' .. hare#GetPath() .. ',,'
let b:undo_ftplugin = 'setl cms< com< flp< fo< inc< inex< isf< pa< sua< mp<'
" Follow the Hare style guide by default.
if get(g:, 'hare_recommended_style', 1) if get(g:, 'hare_recommended_style', 1)
setlocal noexpandtab setlocal noexpandtab
setlocal shiftwidth=0 setlocal shiftwidth=0
setlocal softtabstop=0 setlocal softtabstop=0
setlocal tabstop=8 setlocal tabstop=8
setlocal textwidth=80 setlocal textwidth=80
let b:undo_ftplugin .= ' et< sts< sw< ts< tw<' b:undo_ftplugin ..= ' | setl et< sts< sw< ts< tw<'
endif endif
augroup hare.vim # Highlight incorrect whitespace outside of insert mode.
autocmd! if get(g:, 'hare_space_error', 1)
augroup HareSpaceError
" Highlight whitespace errors by default. autocmd!
if get(g:, 'hare_space_error', 1)
autocmd InsertEnter * hi link hareSpaceError NONE autocmd InsertEnter * hi link hareSpaceError NONE
autocmd InsertLeave * hi link hareSpaceError Error autocmd InsertLeave * hi link hareSpaceError Error
endif augroup END
augroup END
if !exists('current_compiler')
let b:undo_ftplugin .= "| compiler make"
compiler hare
endif endif
let &cpo = s:cpo_save # vim: et sts=2 sw=2 ts=8 tw=80
unlet s:cpo_save
" vim: et sts=2 sw=2 ts=8

View File

@ -1,44 +1,51 @@
" Vim filetype plugin. vim9script
" Language: Haredoc (Hare documentation format)
" Maintainer: Amelia Clarke <selene@perilune.dev> # Vim filetype plugin.
" Last Updated: 2024-05-02 # Language: Haredoc (Hare documentation format)
" Upstream: https://git.sr.ht/~selene/hare.vim # Maintainer: Amelia Clarke <selene@perilune.dev>
# Last Updated: 2025 Sep 06
# Upstream: https://git.sr.ht/~sircmpwn/hare.vim
if exists('b:did_ftplugin') if exists('b:did_ftplugin')
finish finish
endif endif
let b:did_ftplugin = 1 b:did_ftplugin = 1
let s:cpo_save = &cpo # Use the Hare compiler.
set cpo&vim compiler hare
b:undo_ftplugin = 'compiler make'
" Formatting settings. # Formatting settings.
setlocal comments=:\ setlocal comments=:\
setlocal formatlistpat=^\ \\?-\ setlocal commentstring=\ %s
setlocal formatlistpat=^-\
setlocal formatoptions+=tnlj formatoptions-=c formatoptions-=q setlocal formatoptions+=tnlj formatoptions-=c formatoptions-=q
b:undo_ftplugin ..= ' | setl cms< com< flp< fo<'
" Search for Hare modules. # Locate Hare modules.
setlocal includeexpr=hare#FindModule(v:fname) setlocal includeexpr=hare#IncludeExpr()
setlocal isfname+=: setlocal isfname+=:
&l:path = ',,' .. hare#GetPath()
setlocal suffixesadd=.ha setlocal suffixesadd=.ha
b:undo_ftplugin ..= ' | setl inex< isf< pa< sua<'
" Add HAREPATH to the default search paths. # Follow the official style guide by default.
setlocal path-=/usr/include,,
let &l:path .= ',' .. hare#GetPath() .. ',,'
let b:undo_ftplugin = 'setl com< flp< fo< inex< isf< pa< sua<'
" Follow the Hare style guide by default.
if get(g:, 'hare_recommended_style', 1) if get(g:, 'hare_recommended_style', 1)
setlocal noexpandtab setlocal noexpandtab
setlocal shiftwidth=0 setlocal shiftwidth=0
setlocal softtabstop=0 setlocal softtabstop=0
setlocal tabstop=8 setlocal tabstop=8
setlocal textwidth=80 setlocal textwidth=80
let b:undo_ftplugin .= ' et< sts< sw< ts< tw<' b:undo_ftplugin ..= ' | setl et< sts< sw< ts< tw<'
endif endif
let &cpo = s:cpo_save # Highlight incorrect whitespace outside of insert mode.
unlet s:cpo_save if get(g:, 'hare_space_error', 1)
augroup HaredocSpaceError
autocmd!
autocmd InsertEnter * hi link haredocSpaceError NONE
autocmd InsertLeave * hi link haredocSpaceError Error
augroup END
endif
" vim: et sts=2 sw=2 ts=8 # vim: et sts=2 sw=2 ts=8 tw=80

View File

@ -1,146 +1,340 @@
" Vim indent file vim9script
" Language: Hare
" Maintainer: Amelia Clarke <selene@perilune.dev> # Vim indent file.
" Last Change: 2024-04-14 # Language: Hare
" Upstream: https://git.sr.ht/~sircmpwn/hare.vim # Maintainer: Amelia Clarke <selene@perilune.dev>
# Last Change: 2025 Sep 06
# Upstream: https://git.sr.ht/~sircmpwn/hare.vim
if exists('b:did_indent') if exists('b:did_indent')
finish finish
endif endif
let b:did_indent = 1 b:did_indent = 1
let s:cpo_save = &cpo
set cpo&vim
" L0 -> don't deindent labels
" (s -> use one indent after a trailing (
" m1 -> if ) starts a line, indent it the same as its matching (
" ks -> add an extra indent to extra lines in an if expression or for expression
" j1 -> indent code inside {} one level when in parentheses
" J1 -> see j1
" *0 -> don't search for unclosed block comments
" #1 -> don't deindent lines that begin with #
setlocal cinoptions=L0,(s,m1,ks,j1,J1,*0,#1
" Controls which keys reindent the current line.
" 0{ -> { at beginning of line
" 0} -> } at beginning of line
" 0) -> ) at beginning of line
" 0] -> ] at beginning of line
" !^F -> <C-f> (not inserted)
" o -> <CR> or `o` command
" O -> `O` command
" e -> else
" 0=case -> case
setlocal indentkeys=0{,0},0),0],!^F,o,O,e,0=case
setlocal cinwords=if,else,for,switch,match
# L0 -> Don't unindent lines that look like C labels.
# :0 -> Don't indent `case` in match and switch expressions. This only affects
# lines containing `:` (that isn't part of `::`).
# +0 -> Don't indent continuation lines.
# (s -> Indent one level inside parens.
# u0 -> Don't indent additional levels inside nested parens.
# U1 -> Don't treat `(` any differently if it is at the start of a line.
# m1 -> Indent lines starting with `)` the same as the matching `(`.
# j1 -> Indent blocks one level inside parens.
# J1 -> Indent structs and unions correctly.
# *0 -> Don't search for unclosed C-style block comments.
# #1 -> Don't unindent lines starting with `#`.
setlocal cinoptions=L0,:0,+0,(s,u0,U1,m1,j1,J1,*0,#1
setlocal cinscopedecls=
setlocal indentexpr=GetHareIndent() setlocal indentexpr=GetHareIndent()
setlocal indentkeys=0{,0},0),0],!^F,o,O,e,0=case
setlocal nolisp
b:undo_indent = 'setl cino< cinsd< inde< indk< lisp<'
let b:undo_indent = 'setl cino< cinw< inde< indk<' # Calculates the indentation for the current line, using the value computed by
# cindent and manually fixing the cases where it behaves incorrectly.
def GetHareIndent(): number
# Get the preceding lines of context and the value computed by cindent.
const line = getline(v:lnum)
const [plnum, pline] = PrevNonBlank(v:lnum - 1)
const [pplnum, ppline] = PrevNonBlank(plnum - 1)
const pindent = indent(plnum)
const ppindent = indent(pplnum)
const cindent = cindent(v:lnum) / shiftwidth() * shiftwidth()
if exists('*GetHareIndent()') # If this line is a comment, don't try to align it with a comment at the end
finish # of the previous line.
endif if line =~ '^\s*//' && getline(plnum) =~ '\s*//.*$'
return -1
function! FloorCindent(lnum)
return cindent(a:lnum) / shiftwidth() * shiftwidth()
endfunction
function! GetHareIndent()
let line = getline(v:lnum)
let prevlnum = prevnonblank(v:lnum - 1)
let prevline = getline(prevlnum)
let prevprevline = getline(prevnonblank(prevlnum - 1))
" This is all very hacky and imperfect, but it's tough to do much better when
" working with regex-based indenting rules.
" If the previous line ended with =, indent by one shiftwidth.
if prevline =~# '\v\=\s*(//.*)?$'
return indent(prevlnum) + shiftwidth()
endif endif
" If the previous line ended in a semicolon and the line before that ended # Indent `case`.
" with =, deindent by one shiftwidth. if line =~ '^\s*case\>'
if prevline =~# '\v;\s*(//.*)?$' && prevprevline =~# '\v\=\s*(//.*)?$' # If the previous line was also a `case`, use the same indent.
return indent(prevlnum) - shiftwidth() if pline =~ '^\s*case\>'
endif return pindent
" TODO: The following edge-case is still indented incorrectly:
" case =>
" if (foo) {
" bar;
" };
" | // cursor is incorrectly deindented by one shiftwidth.
"
" This only happens if the {} block is the first statement in the case body.
" If `case` is typed, the case will also be incorrectly deindented by one
" shiftwidth. Are you having fun yet?
" Deindent cases.
if line =~# '\v^\s*case'
" If the previous line was also a case, don't do any special indenting.
if prevline =~# '\v^\s*case'
return indent(prevlnum)
end
" If the previous line was a multiline case, deindent by one shiftwidth.
if prevline =~# '\v\=\>\s*(//.*)?$'
return indent(prevlnum) - shiftwidth()
endif endif
" If the previous line started a block, deindent by one shiftwidth. # If the previous line started the block, use the same indent.
" This handles the first case in a switch/match block. if pline =~ '{$'
if prevline =~# '\v\{\s*(//.*)?$' return pindent
return FloorCindent(v:lnum) - shiftwidth()
end
" If the previous line ended in a semicolon and the line before that wasn't
" a case, deindent by one shiftwidth.
if prevline =~# '\v;\s*(//.*)?$' && prevprevline !~# '\v\=\>\s*(//.*)?$'
return FloorCindent(v:lnum) - shiftwidth()
end
let l:indent = FloorCindent(v:lnum)
" If a normal cindent would indent the same amount as the previous line,
" deindent by one shiftwidth. This fixes some issues with `case let` blocks.
if l:indent == indent(prevlnum)
return l:indent - shiftwidth()
endif endif
" Otherwise, do a normal cindent. # If the current line contains a `:` that is not part of `::`, use the
return l:indent # computed cindent.
if line =~ '\v%(%(::)*)@>:'
return cindent
endif
# Unindent after a multi-line `case`.
if pline =~ '=>$'
return pindent - shiftwidth() * GetValue('hare_indent_case', 2)
endif
# If the previous line closed a set of parens, search for the previous
# `case` within the same block and use the same indent. This fixes issues
# with `case` not being correctly unindented after a function call
# continuation line:
#
# case let err: fs::error =>
# fmt::fatalf("Unable to open {}: {}",
# os::args[1], fs::strerror(err));
# case // <-- cindent tries to unindent by only one shiftwidth
if pline =~ ');$'
const case = PrevMatchInBlock('^\s*case\>', plnum - 1)
if case > 0
return indent(case)
endif
endif
# If cindent would indent the same or more than the previous line, unindent.
if cindent >= pindent
return pindent - shiftwidth()
endif
# Otherwise, use the computed cindent.
return cindent
endif endif
" Don't indent an extra shiftwidth for cases which span multiple lines. # Indent after `case`.
if prevline =~# '\v\=\>\s*(//.*)?$' && prevline !~# '\v^\s*case\W' if line !~ '^\s*}'
return indent(prevlnum) # If the previous `case` started and ended on the same line, indent.
if pline =~ '^\s*case\>.*;$'
return pindent + shiftwidth()
endif
# Indent after a single-line `case`.
if pline =~ '^\s*case\>.*=>$'
return pindent + shiftwidth()
endif
# Indent inside a multi-line `case`.
if pline =~ '^\s*case\>' && pline !~ '=>'
return pindent + shiftwidth() * GetValue('hare_indent_case', 2)
endif
# Indent after a multi-line `case`.
if pline =~ '=>$'
return pindent - shiftwidth() * (GetValue('hare_indent_case', 2) - 1)
endif
# Don't unindent while inside a `case` body.
if ppline =~ '=>$' && pline =~ ';$'
return pindent
endif
# Don't unindent if the previous line ended a block. This fixes a very
# peculiar edge case where cindent would try to unindent after a block, but
# only if it is the first expression within a `case` body:
#
# case =>
# if (foo) {
# bar();
# };
# | <-- cindent tries to unindent by one shiftwidth
if pline =~ '};$' && cindent < pindent
return pindent
endif
# If the previous line closed a set of parens, and cindent would try to
# unindent more than one level, search for the previous `case` within the
# same block. If that line didn't contain a `:` (excluding `::`), indent one
# level more. This fixes an issue where cindent would unindent too far when
# there was no `:` after a `case`:
#
# case foo =>
# bar(baz,
# quux);
# | <-- cindent tries to unindent by two shiftwidths
if pline =~ ').*;$' && cindent < pindent - shiftwidth()
const case = PrevMatchInBlock('^\s*case\>', plnum - 1)
if case > 0 && GetTrimmedLine(case) !~ '\v%(%(::)*)@>:'
return indent(case) + shiftwidth()
endif
endif
endif endif
" Indent the body of a case. # If the previous line ended with `=`, indent.
" If the previous line ended in a semicolon and the line before that was a if pline =~ '=$'
" case, don't do any special indenting. return pindent + shiftwidth()
if prevline =~# '\v;\s*(//.*)?$' && prevprevline =~# '\v\=\>\s*(//.*)?$'
\ && line !~# '\v^\s*}'
return indent(prevlnum)
endif endif
let l:indent = FloorCindent(v:lnum) # If the previous line opened an array literal, indent.
if pline =~ '[$'
" If the previous line was a case and a normal cindent wouldn't indent, indent return pindent + shiftwidth()
" an extra shiftwidth.
if prevline =~# '\v\=\>\s*(//.*)?$' && l:indent == indent(prevlnum)
return l:indent + shiftwidth()
endif endif
" If everything above is false, do a normal cindent. # If the previous line started a binding expression, indent.
return l:indent if pline =~ '\v<%(const|def|let|type)$'
endfunction return pindent + shiftwidth()
endif
let &cpo = s:cpo_save # Indent continuation lines.
unlet s:cpo_save if !TrailingParen(pline)
# If this line closed an array and cindent would indent the same amount as
# the previous line, unindent.
if line =~ '^\s*]' && cindent == pindent
return cindent - shiftwidth()
endif
" vim: et sw=2 sts=2 ts=8 # If the previous line closed an array literal, use the same indent. This
# fixes an issue where cindent would try to indent an additional level after
# an array literal containing indexing or slicing expressions, but only
# inside a block:
#
# export fn main() void = {
# const foo = [
# bar[..4],
# baz[..],
# quux[1..],
# ];
# | <-- cindent tries to indent by one shiftwidth
if pline =~ '^\s*];$' && cindent > pindent
return pindent
endif
# Don't indent any further if the previous line closed an enum, struct, or
# union.
if pline =~ '^\s*},$' && cindent > pindent
return pindent
endif
# If the previous line started a binding expression, and the first binding
# was on the same line, indent.
if pline =~ '\v<%(const|def|let|type)>.{-}\=.*,$'
return pindent + shiftwidth()
endif
# Use the original indentation after a single continuation line.
if pline =~ '[,;]$' && ppline =~ '=$'
return ppindent
endif
# Don't unindent within a binding expression.
if pline =~ ',$' && ppline =~ '\v<%(const|def|let|type)$'
return pindent
endif
endif
# If the previous line had an unclosed `if` or `for` condition, indent twice.
if pline =~ '\v<%(if|for)>'
const cond = match(pline, '\v%(if|for)>[^(]*\zs\(')
if cond != -1 && TrailingParen(pline, cond)
return pindent + shiftwidth() * 2
endif
endif
# Optionally indent unclosed `match` and `switch` conditions an extra level.
if pline =~ '\v<%(match|switch)>'
const cond = match(pline, '\v<%(match|switch)>[^(]*\zs\(')
if cond != -1 && TrailingParen(pline, cond)
return pindent + shiftwidth()
* GetValue('hare_indent_match_switch', 1, 1, 2)
endif
endif
# Otherwise, use the computed cindent.
return cindent
enddef
# Returns a line, with any comments or whitespace trimmed from the end.
def GetTrimmedLine(lnum: number): string
var line = getline(lnum)
# Use syntax highlighting attributes when possible.
if has('syntax_items')
# If the last character is inside a comment, do a binary search to find the
# beginning of the comment.
const len = strlen(line)
if synIDattr(synID(lnum, len, true), 'name') =~ 'Comment\|Todo'
var min = 1
var max = len
while min < max
const col = (min + max) / 2
if synIDattr(synID(lnum, col, true), 'name') =~ 'Comment\|Todo'
max = col
else
min = col + 1
endif
endwhile
line = strpart(line, 0, min - 1)
endif
return substitute(line, '\s*$', '', '')
endif
# Otherwise, use a regex as a fallback.
return substitute(line, '\s*//.*$', '', '')
enddef
# Returns the value of a configuration variable, clamped within the given range.
def GetValue(
name: string,
default: number,
min: number = 0,
max: number = default,
): number
const n = get(b:, name, get(g:, name, default))
return min([max, max([n, min])])
enddef
# Returns the line number of the previous match for a pattern within the same
# block. Returns 0 if nothing was found.
def PrevMatchInBlock(
pattern: string,
lnum: number,
maxlines: number = 20,
): number
var block = 0
for n in range(lnum, lnum - maxlines, -1)
if n < 1
break
endif
const line = GetTrimmedLine(n)
if line =~ '{$'
block -= 1
if block < 0
break
endif
endif
if line =~ pattern && block == 0
return n
endif
if line =~ '^\s*}'
block += 1
endif
endfor
return 0
enddef
# Returns the line number and contents of the previous non-blank line, with any
# comments trimmed.
def PrevNonBlank(lnum: number): tuple<number, string>
var plnum = prevnonblank(lnum)
var pline = GetTrimmedLine(plnum)
while plnum > 1 && pline !~ '[^[:blank:]]'
plnum = prevnonblank(plnum - 1)
pline = GetTrimmedLine(plnum)
endwhile
return (plnum, pline)
enddef
# Returns whether a line contains at least one unclosed `(`.
# XXX: Can still be fooled by parens inside rune and string literals.
def TrailingParen(line: string, start: number = 0): bool
var total = 0
for n in strpart(line, start)->filter((_, n) => n =~ '[()]')->reverse()
if n == ')'
total += 1
else
total -= 1
if total < 0
return true
endif
endif
endfor
return false
enddef
# vim: et sts=2 sw=2 ts=8 tw=80

View File

@ -1,157 +1,268 @@
" Vim syntax file. vim9script
" Language: Hare
" Maintainer: Amelia Clarke <selene@perilune.dev> # Vim syntax file.
" Last Change: 2024-05-10 # Language: Hare
" Upstream: https://git.sr.ht/~sircmpwn/hare.vim # Maintainer: Amelia Clarke <selene@perilune.dev>
# Last Change: 2025 Sep 06
# Upstream: https://git.sr.ht/~sircmpwn/hare.vim
if exists('b:current_syntax') if exists('b:current_syntax')
finish finish
endif endif
syn include @haredoc syntax/haredoc.vim
let b:current_syntax = 'hare'
" Syntax {{{1 # Syntax {{{1
syn case match syn case match
syn iskeyword @,48-57,@-@,_ syn iskeyword @,48-57,@-@,_
" Keywords {{{2 # Reserved keywords.
syn keyword hareConditional else if match switch syn cluster hareReserved contains=hareBoolean,hareBuiltin,hareConditional,hareConstant,hareDefine,hareInclude,hareKeyword,hareLabel,hareOperator,hareRepeat,hareStorageClass,hareStructure,hareType,hareTypedef
syn keyword hareDefine def
syn keyword hareInclude use
syn keyword hareKeyword break continue return yield
syn keyword hareKeyword case
syn keyword hareKeyword const let
syn keyword hareKeyword defer
syn keyword hareKeyword export static
syn keyword hareKeyword fn
syn keyword hareOperator as is
syn keyword hareRepeat for
syn keyword hareTypedef type
" Attributes. # Types {{{2
syn keyword hareAttribute @fini @init @test syn cluster hareType contains=hareErrorFlag,harePointer,hareSlice,hareStorageClass,hareStructure,hareTaggedUnion,hareType
syn keyword hareAttribute @offset @packed
syn keyword hareAttribute @symbol
syn keyword hareAttribute @threadlocal
" Builtins.
syn keyword hareBuiltin abort assert
syn keyword hareBuiltin align len offset
syn keyword hareBuiltin alloc free
syn keyword hareBuiltin append delete insert
syn keyword hareBuiltin vaarg vaend vastart
" Types {{{2
syn keyword hareType bool syn keyword hareType bool
syn keyword hareType done syn keyword hareType done
syn keyword hareType f32 f64 syn keyword hareType f32 f64
syn keyword hareType i8 i16 i32 i64 int syn keyword hareType i8 i16 i32 i64 int
syn keyword hareType never syn keyword hareType never
syn keyword hareType nomem
syn keyword hareType opaque syn keyword hareType opaque
syn keyword hareType rune str syn keyword hareType rune str
syn keyword hareType u8 u16 u32 u64 uint syn keyword hareType u8 u16 u32 u64 uint uintptr
syn keyword hareType uintptr
syn keyword hareType valist
syn keyword hareType void syn keyword hareType void
" Other types. # C ABI.
syn keyword hareStorageClass nullable syn keyword hareType valist
# Slice and array types.
syn region hareSlice matchgroup=hareSlice start='\[' end=']' contained containedin=hareBuiltinTypeCall,hareTaggedUnion contains=TOP nextgroup=@hareType skipempty skipwhite
syn match hareSlice '\[[*_]]' contains=hareSliceBounds nextgroup=@hareType skipempty skipwhite
syn match hareSliceBounds '[*_]' contained display
# Other types.
syn keyword hareStorageClass nullable nextgroup=harePointer skipempty skipwhite
syn keyword hareStructure enum struct union syn keyword hareStructure enum struct union
" Literals {{{2 # Declarations {{{2
syn keyword hareBoolean false true syn keyword hareDefine def
syn keyword hareInclude use
syn keyword hareKeyword const nextgroup=@hareType skipempty skipwhite
syn keyword hareKeyword export static
syn keyword hareKeyword fn nextgroup=@hareFunction skipempty skipwhite
syn keyword hareKeyword let
syn keyword hareTypedef type nextgroup=hareTypeIdentifier skipempty skipwhite
# Function declarations.
syn cluster hareFunction contains=hareFunction,hareFuncParams
syn match hareFunction '\v<\h\w*%(::\h\w*)*>' contained contains=@hareIdentifier nextgroup=hareFuncParams skipempty skipwhite
syn region hareFuncParams matchgroup=hareFuncParams start='(' end=')' contained contains=TOP nextgroup=@hareType skipempty skipwhite
# Type declarations.
# FIXME: Does not yet account for type declarations with multiple bindings.
syn match hareTypeIdentifier '\v<\h\w*%(::\h\w*)*>' contained contains=hareIdentifier nextgroup=hareTypeEquals skipempty skipwhite transparent
syn match hareTypeEquals '=' contained nextgroup=@hareType skipempty skipwhite transparent
# Identifiers.
syn match hareIdentifier '\v<\h\w*%(::\h\w*)*>' contains=@hareIdentifier nextgroup=@harePostfix skipempty skipwhite
syn cluster hareIdentifier contains=hareDelimiter,hareName
syn match hareName '\<\h\w*\>' contained contains=@hareReserved transparent
# Attributes {{{3
syn keyword hareAttribute @init @fini @test
syn keyword hareAttribute @offset nextgroup=hareAttrParens skipempty skipwhite
syn keyword hareAttribute @packed
syn keyword hareAttribute @symbol nextgroup=hareAttrParens skipempty skipwhite
syn keyword hareAttribute @threadlocal
# Match the parens after attributes.
syn region hareAttrParens matchgroup=hareAttrParens start='(' end=')' contained contains=TOP
# Expressions {{{2
syn keyword hareConditional else
syn keyword hareConditional if nextgroup=hareCondParens skipempty skipwhite
syn keyword hareConditional match switch nextgroup=@hareCondition skipempty skipwhite
syn keyword hareKeyword break continue return yield
syn keyword hareKeyword defer
syn keyword hareLabel case nextgroup=@hareType skipempty skipwhite
syn keyword hareOperator as is nextgroup=@hareType skipempty skipwhite
syn keyword hareRepeat for nextgroup=@hareCondition skipempty skipwhite
# Match the parens in conditionals and for-loops.
syn cluster hareCondition contains=hareCondLabel,hareCondParens
syn match hareCondLabel ':\h\w*\>' contained contains=hareUserLabel nextgroup=hareCondParens skipempty skipwhite transparent
syn region hareCondParens matchgroup=hareCondParens start='(' end=')' contained contains=TOP
# Builtins {{{3
syn keyword hareBuiltin abort assert nextgroup=hareBuiltinCall skipempty skipwhite
syn keyword hareBuiltin align nextgroup=hareBuiltinTypeCall skipempty skipwhite
syn keyword hareBuiltin alloc free nextgroup=hareBuiltinCall skipempty skipwhite
syn keyword hareBuiltin append insert delete nextgroup=hareBuiltinCall skipempty skipwhite
syn keyword hareBuiltin len offset nextgroup=hareBuiltinCall skipempty skipwhite
# C ABI.
syn keyword hareBuiltin vastart vaarg vaend nextgroup=hareBuiltinCall skipempty skipwhite
# Highlight `size` as a builtin only if it is followed by an open paren.
syn match hareType '\<size\>'
syn match hareBuiltin '\<size\ze(' nextgroup=hareBuiltinTypeCall
# Match the parens in builtin expressions.
syn region hareBuiltinCall matchgroup=hareBuiltinCall start='(' end=')' contained contains=TOP nextgroup=@harePostfix skipempty skipwhite
syn region hareBuiltinTypeCall matchgroup=hareBuiltinTypeCall start='(' end=')' contained contains=TOP nextgroup=@harePostfix skipempty skipwhite
# Operators {{{3
syn match hareSymbolOperator '\.\{2,3}'
syn match hareSymbolOperator '[!<=>]=\?'
syn match hareSymbolOperator '=>'
# Additive and multiplicative arithmetic.
syn match hareSymbolOperator '[-+*/%]=\?'
# Bit-shifting arithmetic.
syn match hareSymbolOperator '\%(<<\|>>\)=\?'
# Bitwise arithmetic.
syn match hareSymbolOperator '[&^|]=\?'
syn match hareSymbolOperator '\~'
# Logical arithmetic.
syn match hareSymbolOperator '\%(&&\|^^\|||\)=\?'
# Highlight `!`, `*`, and `|` correctly in types.
syn match hareErrorFlag '!' contained containedin=hareBuiltinTypeCall,hareTaggedUnion nextgroup=@hareType skipempty skipwhite
syn match harePointer '*' contained containedin=hareBuiltinTypeCall,hareTaggedUnion nextgroup=@hareType skipempty skipwhite
syn match hareTaggedUnionBar '|' contained containedin=hareTaggedUnion
# Postfix expressions {{{3
# TODO: Match postfix expressions after literals.
syn cluster harePostfix contains=hareCast,hareErrorCheck,hareFieldAccess,hareFuncCall,hareIndex
# Casts and type hints.
syn match hareCast ':' nextgroup=@hareType skipempty skipwhite
# Error handling.
syn match hareErrorCheck '!=\@!' contained nextgroup=@harePostfix skipempty skipwhite
syn match hareErrorCheck '?' nextgroup=@harePostfix skipempty skipwhite
# Field access.
syn match hareFieldAccess '\.\w\+\>' contained contains=hareName,hareNumber nextgroup=@harePostfix skipempty skipwhite
# Function calls.
syn region hareFuncCall matchgroup=hareFuncCall start='(' end=')' contained contains=TOP nextgroup=@harePostfix skipempty skipwhite
# Indexing and slicing.
syn region hareIndex matchgroup=hareIndex start='\[' end=']' contained contains=TOP nextgroup=@harePostfix skipempty skipwhite
# Nested expressions.
syn region hareParens matchgroup=hareParens start='(' end=')' contains=TOP nextgroup=@harePostfix skipempty skipwhite
# Tagged union and tuple types.
syn region hareTaggedUnion matchgroup=hareTaggedUnion start='(' end=')' contained containedin=hareBuiltinTypeCall,hareTaggedUnion contains=TOP
# Literals {{{3
syn keyword hareBoolean true false
syn keyword hareConstant null syn keyword hareConstant null
" Integer literals. # Integers.
syn match hareNumber '\v<%(0|[1-9]%(_?\d)*)%([Ee]\+?\d+)?%([iu]%(8|16|32|64)?|z)?>' display syn match hareNumber '\v<%(0|[1-9]%(_?\d)*)%([Ee]\+?\d+)?%([iu]%(8|16|32|64)?|z)?>'
syn match hareNumber '\v<0b[01]%(_?[01])*%([iu]%(8|16|32|64)?|z)?>' display syn match hareNumber '\v<0b[01]%(_?[01])*%([iu]%(8|16|32|64)?|z)?>'
syn match hareNumber '\v<0o\o%(_?\o)*%([iu]%(8|16|32|64)?|z)?>' display syn match hareNumber '\v<0o\o%(_?\o)*%([iu]%(8|16|32|64)?|z)?>'
syn match hareNumber '\v<0x\x%(_?\x)*%([iu]%(8|16|32|64)?|z)?>' display syn match hareNumber '\v<0x\x%(_?\x)*%([iu]%(8|16|32|64)?|z)?>'
" Floating-point literals. # Floats.
syn match hareFloat '\v<%(0|[1-9]%(_?\d)*)\.\d%(_?\d)*%([Ee][+-]?\d+)?%(f32|f64)?>' display syn match hareFloat '\v<%(0|[1-9]%(_?\d)*)\.\d%(_?\d)*%([Ee][+-]?\d+)?%(f32|f64)?>'
syn match hareFloat '\v<%(0|[1-9]%(_?\d)*)%([Ee][+-]?\d+)?%(f32|f64)>' display syn match hareFloat '\v<%(0|[1-9]%(_?\d)*)%([Ee][+-]?\d+)?%(f32|f64)>'
syn match hareFloat '\v<0x\x%(_?\x)*%(\.\x%(_?\x)*)?[Pp][+-]?\d+%(f32|f64)?>' display syn match hareFloat '\v<%(0|[1-9]%(_?\d)*)[Ee]-\d+>'
syn match hareFloat '\v<0x\x%(_?\x)*%(\.\x%(_?\x)*)?[Pp][+-]?\d+%(f32|f64)?>'
" Rune and string literals. # Rune and string literals.
syn region hareRune start="'" skip="\\'" end="'" contains=hareEscape syn region hareRune start="'" skip="\\'" end="'" contains=hareEscape
syn region hareString start='"' skip='\\"' end='"' contains=hareEscape,hareFormat syn region hareString start='"' skip='\\"' end='"' contains=hareEscape,hareFormat
syn region hareString start='`' end='`' contains=hareFormat syn region hareString start='`' end='`' contains=hareFormat
" Escape sequences. # Escape sequences.
syn match hareEscape '\\[0abfnrtv\\'"]' contained syn match hareEscape '\\[0abfnrtv\\'"]' contained
syn match hareEscape '\v\\%(x\x{2}|u\x{4}|U\x{8})' contained display syn match hareEscape '\v\\%(x\x{2}|u\x{4}|U\x{8})' contained display
" Format sequences. # Format sequences.
syn match hareFormat '\v\{\d*%(:%(\.?\d+|[ +\-=Xbefgox]|F[.2ESUs]|_%(.|\\%([0abfnrtv\\'"]|x\x{2}|u\x{4}|U\x{8})))*)?}' contained contains=hareEscape display syn match hareFormat '\v\{\d*%(:%(\.?\d+|[- +=Xbefgox]|F[.2ESUs]|_%(\_.|\\%([0abfnrtv\'"]|x\x{2}|u\x{4}|U\x{8})))*)?}' contained contains=hareEscape
syn match hareFormat '{\d*%\d*}' contained display syn match hareFormat '{\d*%\d*}' contained display
syn match hareFormat '{{\|}}' contained display syn match hareFormat '{{\|}}' contained
" Miscellaneous {{{2 # Miscellaneous {{{2
" Comments. # Annotations.
syn region hareComment start='//' end='$' contains=hareTodo,@haredoc,@Spell display syn region hareAnnotation start='#\[' end=']' contains=hareAnnotationIdentifier
syn match hareAnnotationIdentifier '\v<\h\w*%(::\h\w*)*>' contained contains=@hareIdentifier nextgroup=hareAnnotationParens skipempty skipwhite transparent
syn region hareAnnotationParens matchgroup=hareAnnotationParens start='(' end=')' contained contains=TOP
# Blocks.
syn region hareBlock matchgroup=hareBlock start='{' end='}' contains=TOP fold nextgroup=@harePostfix skipempty skipwhite
# Comments.
syn region hareComment start='//' end='$' contains=@hareComment keepend
syn cluster hareComment contains=hareCommentCode,hareCommentRef,hareTodo,@Spell
syn region hareCommentCode start='\t\zs' end='$' contained contains=@NoSpell display
syn match hareCommentRef '\v\[\[\h\w*%(::\h\w*)*%(::)?]]' contained contains=@NoSpell display
syn keyword hareTodo FIXME TODO XXX contained syn keyword hareTodo FIXME TODO XXX contained
" Identifiers. # Delimiters.
syn match hareDelimiter '::' display syn match hareDelimiter '::'
syn match hareName '\<\h\w*\>' nextgroup=@harePostfix skipempty skipwhite transparent
" Labels. # Labels.
syn match hareLabel ':\h\w*\>' display syn match hareUserLabel ':\h\w*\>' contains=hareName
" Match `size` as a type unless it is followed by an open paren. # Default highlighting {{{1
syn match hareType '\<size\>' display hi def link hareAnnotation PreProc
syn match hareBuiltin '\<size\ze(' display hi def link hareAnnotationParens hareAnnotation
" Postfix expressions.
syn cluster harePostfix contains=hareErrorTest,hareField,hareIndex,hareParens
syn match hareErrorTest '!=\@!' contained nextgroup=@harePostfix skipempty skipwhite
syn match hareErrorTest '?' nextgroup=@harePostfix skipempty skipwhite
syn match hareField '\.\w*\>'hs=s+1 contained contains=hareNumber nextgroup=@harePostfix skipempty skipwhite
syn region hareIndex start='\[' end=']' contained nextgroup=@harePostfix skipempty skipwhite transparent
syn region hareParens start='(' end=')' nextgroup=@harePostfix skipempty skipwhite transparent
" Whitespace errors.
syn match hareSpaceError '^ \+\ze\t' display
syn match hareSpaceError excludenl '\s\+$' containedin=ALL display
" Folding {{{3
syn region hareBlock start='{' end='}' fold transparent
" Default highlighting {{{1
hi def link hareAttribute PreProc hi def link hareAttribute PreProc
hi def link hareBoolean Boolean hi def link hareBoolean Boolean
hi def link hareBuiltin Operator hi def link hareBuiltin Operator
hi def link hareComment Comment hi def link hareComment Comment
hi def link hareCommentCode hareComment
hi def link hareCommentRef SpecialComment
hi def link hareConditional Conditional hi def link hareConditional Conditional
hi def link hareConstant Constant hi def link hareConstant Constant
hi def link hareDefine Define hi def link hareDefine Define
hi def link hareDelimiter Delimiter hi def link hareDelimiter Delimiter
hi def link hareErrorTest Special hi def link hareErrorFlag hareStorageClass
hi def link hareErrorCheck Special
hi def link hareEscape SpecialChar hi def link hareEscape SpecialChar
hi def link hareFloat Float hi def link hareFloat Float
hi def link hareFormat SpecialChar hi def link hareFormat SpecialChar
hi def link hareFunction Function
hi def link hareInclude Include hi def link hareInclude Include
hi def link hareKeyword Keyword hi def link hareKeyword Keyword
hi def link hareLabel Special hi def link hareLabel Label
hi def link hareNumber Number hi def link hareNumber Number
hi def link hareOperator Operator hi def link hareOperator Operator
hi def link harePointer hareStorageClass
hi def link hareRepeat Repeat hi def link hareRepeat Repeat
hi def link hareRune Character hi def link hareRune Character
hi def link hareSliceBounds harePointer
hi def link hareStorageClass StorageClass hi def link hareStorageClass StorageClass
hi def link hareString String hi def link hareString String
hi def link hareStructure Structure hi def link hareStructure Structure
hi def link hareTodo Todo hi def link hareTodo Todo
hi def link hareType Type hi def link hareType Type
hi def link hareTypedef Typedef hi def link hareTypedef Typedef
hi def link hareUserLabel Identifier
" Highlight embedded haredoc references. # Optionally highlight symbolic operators.
hi! def link haredocRefValid SpecialComment if get(g:, 'hare_symbol_operators')
hi! def link hareSymbolOperator hareOperator
" Highlight whitespace errors by default. else
if get(g:, 'hare_space_error', 1) hi! def link hareSymbolOperator NONE
hi def link hareSpaceError Error
endif endif
" vim: et sts=2 sw=2 ts=8 # Highlight incorrect whitespace by default.
syn match hareSpaceError '\s\+$' containedin=ALL display
syn match hareSpaceError ' \+\ze\t' display
if get(g:, 'hare_space_error', 1)
hi! def link hareSpaceError Error
else
hi! def link hareSpaceError NONE
endif
b:current_syntax = 'hare'
# vim: fdm=marker et sts=2 sw=2 ts=8 tw=80

View File

@ -1,32 +1,43 @@
" Vim syntax file. vim9script
" Language: Haredoc (Hare documentation format)
" Maintainer: Amelia Clarke <selene@perilune.dev> # Vim syntax file.
" Last Change: 2024-05-10 # Language: Haredoc (Hare documentation format)
" Upstream: https://git.sr.ht/~selene/hare.vim # Maintainer: Amelia Clarke <selene@perilune.dev>
# Last Change: 2025 Aug 14
# Upstream: https://git.sr.ht/~sircmpwn/hare.vim
if exists('b:current_syntax') if exists('b:current_syntax')
finish finish
endif endif
let b:current_syntax = 'haredoc'
" Syntax {{{1 # Syntax {{{1
syn case match syn case match
syn iskeyword @,48-57,_ syn iskeyword @,48-57,_
" Code samples. # Embedded code samples.
syn region haredocCodeSample excludenl start='\t\zs' end='$' contains=@NoSpell display syn region haredocCode start='\t\zs' end='$' contains=@NoSpell display
" References to other declarations and modules. # References to other declarations and modules.
syn region haredocRef start='\[\[' end=']]' contains=haredocRefValid,@NoSpell display keepend oneline syn match haredocRef '\v\[\[\h\w*%(::\h\w*)*%(::)?]]' contains=@NoSpell display
syn match haredocRefValid '\v\[\[\h\w*%(::\h\w*)*%(::)?]]' contained contains=@NoSpell display
" Miscellaneous. # Miscellaneous.
syn keyword haredocTodo FIXME TODO XXX syn keyword haredocTodo FIXME TODO XXX
" Default highlighting {{{1 # Default highlighting {{{1
hi def link haredocCodeSample Comment hi def link haredocCode Comment
hi def link haredocRef Error hi def link haredocRef Special
hi def link haredocRefValid Special
hi def link haredocTodo Todo hi def link haredocTodo Todo
" vim: et sts=2 sw=2 ts=8 # Highlight incorrect whitespace by default.
syn match haredocSpaceError '\s\+$' containedin=ALL display
syn match haredocSpaceError '^ \zs \+\ze\t' containedin=ALL display
syn match haredocSpaceError '[^ ]\zs \+\ze\t' containedin=ALL display
if get(g:, 'hare_space_error', 1)
hi! def link haredocSpaceError Error
else
hi! def link haredocSpaceError NONE
endif
b:current_syntax = 'haredoc'
# vim: fdm=marker et sts=2 sw=2 ts=8 tw=80

View File

@ -1708,6 +1708,7 @@ endfunc
func Test_haredoc_file() func Test_haredoc_file()
filetype on filetype on
call assert_true(mkdir('foo/bar', 'pR')) call assert_true(mkdir('foo/bar', 'pR'))
call writefile([], 'README', 'D') call writefile([], 'README', 'D')
@ -1715,28 +1716,37 @@ func Test_haredoc_file()
call assert_notequal('haredoc', &filetype) call assert_notequal('haredoc', &filetype)
bwipe! bwipe!
let g:filetype_haredoc = 3
call writefile([], 'foo/bar/bar.ha', 'D')
split README
call assert_equal('haredoc', &filetype)
bwipe!
let g:filetype_haredoc = 2
split README
call assert_notequal('haredoc', &filetype)
bwipe!
call writefile([], 'foo/foo.ha', 'D')
split README
call assert_equal('haredoc', &filetype)
bwipe!
let g:filetype_haredoc = 1 let g:filetype_haredoc = 1
split README split README
call assert_notequal('haredoc', &filetype) call assert_notequal('haredoc', &filetype)
bwipe! bwipe!
call writefile([], 'foo/quux.ha') call writefile([], 'main.ha', 'D')
split README split README
call assert_equal('haredoc', &filetype) call assert_equal('haredoc', &filetype)
bwipe! bwipe!
call delete('foo/quux.ha')
call writefile([], 'foo/bar/baz.ha', 'D') let g:filetype_haredoc = 0
split README split README
call assert_notequal('haredoc', &filetype) call assert_notequal('haredoc', &filetype)
bwipe! bwipe!
let g:haredoc_search_depth = 2
split README
call assert_equal('haredoc', &filetype)
bwipe!
unlet g:filetype_haredoc unlet g:filetype_haredoc
unlet g:haredoc_search_depth
filetype off filetype off
endfunc endfunc