0
0
mirror of https://github.com/vim/vim.git synced 2025-10-21 08:24:06 -04:00

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
#
# 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>
# These functions are moved here from runtime/filetype.vim to make startup
@@ -441,30 +441,30 @@ export def FTfs()
endif
enddef
# Recursively search for Hare source files in a directory and any
# subdirectories, up to a given depth.
# Recursively searches for Hare source files within a directory, up to a given
# depth.
def IsHareModule(dir: string, depth: number): bool
if depth <= 0
return !empty(glob(dir .. '/*.ha'))
if depth < 1
return false
elseif depth == 1
return !glob(dir .. '/*.ha')->empty()
endif
return reduce(sort(glob(dir .. '/*', true, true),
(a, b) => isdirectory(a) - isdirectory(b)),
(acc, n) => acc
# Check all files in the directory before recursing into subdirectories.
return glob(dir .. '/*', true, true)
->sort((a, b) => isdirectory(a) - isdirectory(b))
->reduce((acc, n) => acc
|| n =~ '\.ha$'
|| isdirectory(n)
&& IsHareModule(n, depth - 1),
|| isdirectory(n) && IsHareModule(n, depth - 1),
false)
enddef
# Determine if a README file exists within a Hare module and should be given the
# Haredoc filetype.
# Determines whether a README file is inside a Hare module and should receive
# the 'haredoc' filetype.
export def FTharedoc()
if exists('g:filetype_haredoc')
if IsHareModule('<afile>:h', get(g:, 'haredoc_search_depth', 1))
if IsHareModule('<afile>:h', get(g:, 'filetype_haredoc', 1))
setf haredoc
endif
endif
enddef
# Distinguish between HTML, XHTML, Django and Angular

View File

@@ -1,26 +1,82 @@
" Vim autoload file.
" Language: Hare
" Maintainer: Amelia Clarke <selene@perilune.dev>
" Last Updated: 2024-05-10
" Upstream: https://git.sr.ht/~sircmpwn/hare.vim
vim9script
" Attempt to find the directory for a given Hare module.
function hare#FindModule(str)
let path = substitute(trim(a:str, ':', 2), '::', '/', 'g')
let dir = finddir(path)
while !empty(path) && empty(dir)
let path = substitute(path, '/\?\h\w*$', '', '')
let dir = finddir(path)
endwhile
return dir
endfunction
# Helper functions for Hare.
# Language: Hare
# Maintainer: Amelia Clarke <selene@perilune.dev>
# Last Updated: 2025 Sep 06
# Upstream: https://git.sr.ht/~sircmpwn/hare.vim
" Return the value of HAREPATH if it exists. Otherwise use a reasonable default.
function hare#GetPath()
if empty($HAREPATH)
# Returns the value of HAREPATH, if it exists. Otherwise, returns a safe
# default.
export def GetPath(): string
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
return substitute($HAREPATH, ':', ',', 'g')
endfunction
endif
return mapnew(path, (_, n) => escape(n, ' ,;'))->join(',')
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.
" Compiler: Hare
" Maintainer: Amelia Clarke <selene@perilune.dev>
" Last Change: 2024-05-23
" Upstream: https://git.sr.ht/~sircmpwn/hare.vim
vim9script
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
endif
let current_compiler = 'hare'
let s:cpo_save = &cpo
set cpo&vim
if filereadable('Makefile') || filereadable('makefile')
CompilerSet makeprg=make
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
CompilerSet errorformat=
\%f:%l:%c:\ syntax\ error:\ %m,
\%f:%l:%c:\ error:\ %m,
\%o:%l:%v:\ syntax\ error:\ %m,
\%o:%l:%v:\ error:\ %m,
\Error:\ %m,
\%-G%.%#
let &cpo = s:cpo_save
unlet s:cpo_save
augroup HareQuickFix
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
==============================================================================
CONTENTS *hare*
CONTENTS *hare* *hare.vim*
1. Introduction |hare-intro|
2. Filetype plugin |hare-plugin|
3. Settings |hare-settings|
1. Introduction |ft-hare-intro|
2. Filetype plugin |ft-hare-plugin|
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
for the Hare programming language. Support is also provided for README files
inside Hare modules, but this must be enabled by setting |g:filetype_haredoc|.
This plugin provides syntax highlighting, indentation, and other supporting
functionality for the Hare programming language.
==============================================================================
FILETYPE PLUGIN *hare-plugin*
This plugin automatically sets the value of 'path' to include the contents of
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.
FILETYPE PLUGIN *ft-hare-plugin*
==============================================================================
SETTINGS *hare-settings*
This plugin has a few different variables that can be defined inside your
|vimrc| to tweak its behavior.
This plugin provides a small number of variables that you can define in your
vimrc to configure its behavior.
Additionally, support is provided for folding `{ }` blocks. To enable folding,
add the following to a file inside your |after-directory| (e.g.
~/.vim/after/ftplugin/hare.vim): >
*g:filetype_haredoc*
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
heuristic has a minor performance impact, this feature is disabled by default
and must be specifically opted into: >
let g:filetype_haredoc = 1
<
See |g:haredoc_search_depth| for ways to tweak the searching behavior.
setlocal foldmethod=syntax
Because block-based folding tends to create many small folds, consider setting
a few related options, such as 'foldminlines' and 'foldnestmax'.
*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: >
setlocal noexpandtab
setlocal shiftwidth=0
setlocal softtabstop=0
setlocal tabstop=8
setlocal textwidth=80
<
To disable this behavior: >
To disable this behavior, add the following to your |vimrc|: >
let g:hare_recommended_style = 0
<
*g:hare_space_error*
By default, trailing whitespace and tabs preceded by space characters are
highlighted as errors. This is automatically turned off when in insert mode.
To disable this highlighting completely: >
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.
*g:hare_symbol_operators*
By default, symbolic operators do not receive any special highlighting (with
`!`, `?`, and `::` being the only exceptions). To enable syntax highlighting
for most other operators, add the following to your |vimrc|: >
The maximum search depth can be set to any integer, but using values higher
than 2 is not recommended, and will likely provide no tangible benefit in most
situations.
let g:hare_symbol_operators = 1
<
*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-gnat ft_ada.txt /*compiler-gnat*
compiler-groff quickfix.txt /*compiler-groff*
compiler-hare ft_hare.txt /*compiler-hare*
compiler-hpada ft_ada.txt /*compiler-hpada*
compiler-javac quickfix.txt /*compiler-javac*
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-gsp-syntax syntax.txt /*ft-gsp-syntax*
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-help-omni helphelp.txt /*ft-help-omni*
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_Command ft_ada.txt /*g:gnat.Tags_Command*
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_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:html_charset_override syntax.txt /*g:html_charset_override*
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*
hangulin.txt hangulin.txt /*hangulin.txt*
hare ft_hare.txt /*hare*
hare-intro ft_hare.txt /*hare-intro*
hare-plugin ft_hare.txt /*hare-plugin*
hare-settings ft_hare.txt /*hare-settings*
hare.vim ft_hare.txt /*hare.vim*
has() builtin.txt /*has()*
has-patch builtin.txt /*has-patch*
has-python if_pyth.txt /*has-python*

View File

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

View File

@@ -1,44 +1,51 @@
" Vim filetype plugin.
" Language: Haredoc (Hare documentation format)
" Maintainer: Amelia Clarke <selene@perilune.dev>
" Last Updated: 2024-05-02
" Upstream: https://git.sr.ht/~selene/hare.vim
vim9script
# Vim filetype plugin.
# Language: Haredoc (Hare documentation format)
# Maintainer: Amelia Clarke <selene@perilune.dev>
# Last Updated: 2025 Sep 06
# Upstream: https://git.sr.ht/~sircmpwn/hare.vim
if exists('b:did_ftplugin')
finish
endif
let b:did_ftplugin = 1
b:did_ftplugin = 1
let s:cpo_save = &cpo
set cpo&vim
# Use the Hare compiler.
compiler hare
b:undo_ftplugin = 'compiler make'
" Formatting settings.
# Formatting settings.
setlocal comments=:\
setlocal formatlistpat=^\ \\?-\
setlocal commentstring=\ %s
setlocal formatlistpat=^-\
setlocal formatoptions+=tnlj formatoptions-=c formatoptions-=q
b:undo_ftplugin ..= ' | setl cms< com< flp< fo<'
" Search for Hare modules.
setlocal includeexpr=hare#FindModule(v:fname)
# Locate Hare modules.
setlocal includeexpr=hare#IncludeExpr()
setlocal isfname+=:
&l:path = ',,' .. hare#GetPath()
setlocal suffixesadd=.ha
b:undo_ftplugin ..= ' | setl inex< isf< pa< sua<'
" Add HAREPATH to the default search paths.
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.
# Follow the official style guide by default.
if get(g:, 'hare_recommended_style', 1)
setlocal noexpandtab
setlocal shiftwidth=0
setlocal softtabstop=0
setlocal tabstop=8
setlocal textwidth=80
let b:undo_ftplugin .= ' et< sts< sw< ts< tw<'
b:undo_ftplugin ..= ' | setl et< sts< sw< ts< tw<'
endif
let &cpo = s:cpo_save
unlet s:cpo_save
# Highlight incorrect whitespace outside of insert mode.
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
" Language: Hare
" Maintainer: Amelia Clarke <selene@perilune.dev>
" Last Change: 2024-04-14
" Upstream: https://git.sr.ht/~sircmpwn/hare.vim
vim9script
# Vim indent file.
# Language: Hare
# Maintainer: Amelia Clarke <selene@perilune.dev>
# Last Change: 2025 Sep 06
# Upstream: https://git.sr.ht/~sircmpwn/hare.vim
if exists('b:did_indent')
finish
endif
let 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
b:did_indent = 1
# 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 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()')
finish
# If this line is a comment, don't try to align it with a comment at the end
# of the previous line.
if line =~ '^\s*//' && getline(plnum) =~ '\s*//.*$'
return -1
endif
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()
# Indent `case`.
if line =~ '^\s*case\>'
# If the previous line was also a `case`, use the same indent.
if pline =~ '^\s*case\>'
return pindent
endif
" If the previous line ended in a semicolon and the line before that ended
" with =, deindent by one shiftwidth.
if prevline =~# '\v;\s*(//.*)?$' && prevprevline =~# '\v\=\s*(//.*)?$'
return indent(prevlnum) - shiftwidth()
# If the previous line started the block, use the same indent.
if pline =~ '{$'
return pindent
endif
" 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()
# If the current line contains a `:` that is not part of `::`, use the
# computed cindent.
if line =~ '\v%(%(::)*)@>:'
return cindent
endif
" If the previous line started a block, deindent by one shiftwidth.
" This handles the first case in a switch/match block.
if prevline =~# '\v\{\s*(//.*)?$'
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()
# Unindent after a multi-line `case`.
if pline =~ '=>$'
return pindent - shiftwidth() * GetValue('hare_indent_case', 2)
endif
" Otherwise, do a normal cindent.
return l:indent
# 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
" Don't indent an extra shiftwidth for cases which span multiple lines.
if prevline =~# '\v\=\>\s*(//.*)?$' && prevline !~# '\v^\s*case\W'
return indent(prevlnum)
# If cindent would indent the same or more than the previous line, unindent.
if cindent >= pindent
return pindent - shiftwidth()
endif
" Indent the body of a case.
" If the previous line ended in a semicolon and the line before that was a
" case, don't do any special indenting.
if prevline =~# '\v;\s*(//.*)?$' && prevprevline =~# '\v\=\>\s*(//.*)?$'
\ && line !~# '\v^\s*}'
return indent(prevlnum)
# Otherwise, use the computed cindent.
return cindent
endif
let l:indent = FloorCindent(v:lnum)
" If the previous line was a case and a normal cindent wouldn't indent, indent
" an extra shiftwidth.
if prevline =~# '\v\=\>\s*(//.*)?$' && l:indent == indent(prevlnum)
return l:indent + shiftwidth()
# Indent after `case`.
if line !~ '^\s*}'
# If the previous `case` started and ended on the same line, indent.
if pline =~ '^\s*case\>.*;$'
return pindent + shiftwidth()
endif
" If everything above is false, do a normal cindent.
return l:indent
endfunction
# Indent after a single-line `case`.
if pline =~ '^\s*case\>.*=>$'
return pindent + shiftwidth()
endif
let &cpo = s:cpo_save
unlet s:cpo_save
# Indent inside a multi-line `case`.
if pline =~ '^\s*case\>' && pline !~ '=>'
return pindent + shiftwidth() * GetValue('hare_indent_case', 2)
endif
" vim: et sw=2 sts=2 ts=8
# 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
# If the previous line ended with `=`, indent.
if pline =~ '=$'
return pindent + shiftwidth()
endif
# If the previous line opened an array literal, indent.
if pline =~ '[$'
return pindent + shiftwidth()
endif
# If the previous line started a binding expression, indent.
if pline =~ '\v<%(const|def|let|type)$'
return pindent + shiftwidth()
endif
# Indent continuation lines.
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
# 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.
" Language: Hare
" Maintainer: Amelia Clarke <selene@perilune.dev>
" Last Change: 2024-05-10
" Upstream: https://git.sr.ht/~sircmpwn/hare.vim
vim9script
# Vim syntax file.
# Language: Hare
# Maintainer: Amelia Clarke <selene@perilune.dev>
# Last Change: 2025 Sep 06
# Upstream: https://git.sr.ht/~sircmpwn/hare.vim
if exists('b:current_syntax')
finish
endif
syn include @haredoc syntax/haredoc.vim
let b:current_syntax = 'hare'
" Syntax {{{1
# Syntax {{{1
syn case match
syn iskeyword @,48-57,@-@,_
" Keywords {{{2
syn keyword hareConditional else if match switch
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
# Reserved keywords.
syn cluster hareReserved contains=hareBoolean,hareBuiltin,hareConditional,hareConstant,hareDefine,hareInclude,hareKeyword,hareLabel,hareOperator,hareRepeat,hareStorageClass,hareStructure,hareType,hareTypedef
" Attributes.
syn keyword hareAttribute @fini @init @test
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
# Types {{{2
syn cluster hareType contains=hareErrorFlag,harePointer,hareSlice,hareStorageClass,hareStructure,hareTaggedUnion,hareType
syn keyword hareType bool
syn keyword hareType done
syn keyword hareType f32 f64
syn keyword hareType i8 i16 i32 i64 int
syn keyword hareType never
syn keyword hareType nomem
syn keyword hareType opaque
syn keyword hareType rune str
syn keyword hareType u8 u16 u32 u64 uint
syn keyword hareType uintptr
syn keyword hareType valist
syn keyword hareType u8 u16 u32 u64 uint uintptr
syn keyword hareType void
" Other types.
syn keyword hareStorageClass nullable
# C ABI.
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
" Literals {{{2
syn keyword hareBoolean false true
# Declarations {{{2
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
" Integer literals.
syn match hareNumber '\v<%(0|[1-9]%(_?\d)*)%([Ee]\+?\d+)?%([iu]%(8|16|32|64)?|z)?>' display
syn match hareNumber '\v<0b[01]%(_?[01])*%([iu]%(8|16|32|64)?|z)?>' display
syn match hareNumber '\v<0o\o%(_?\o)*%([iu]%(8|16|32|64)?|z)?>' display
syn match hareNumber '\v<0x\x%(_?\x)*%([iu]%(8|16|32|64)?|z)?>' display
# Integers.
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)?>'
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)?>'
" Floating-point literals.
syn match hareFloat '\v<%(0|[1-9]%(_?\d)*)\.\d%(_?\d)*%([Ee][+-]?\d+)?%(f32|f64)?>' display
syn match hareFloat '\v<%(0|[1-9]%(_?\d)*)%([Ee][+-]?\d+)?%(f32|f64)>' display
syn match hareFloat '\v<0x\x%(_?\x)*%(\.\x%(_?\x)*)?[Pp][+-]?\d+%(f32|f64)?>' display
# Floats.
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)>'
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 hareString start='"' skip='\\"' end='"' contains=hareEscape,hareFormat
syn region hareString start='`' end='`' contains=hareFormat
" Escape sequences.
# Escape sequences.
syn match hareEscape '\\[0abfnrtv\\'"]' contained
syn match hareEscape '\v\\%(x\x{2}|u\x{4}|U\x{8})' contained display
" 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
# Format sequences.
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 '{{\|}}' contained display
syn match hareFormat '{{\|}}' contained
" Miscellaneous {{{2
# Miscellaneous {{{2
" Comments.
syn region hareComment start='//' end='$' contains=hareTodo,@haredoc,@Spell display
# Annotations.
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
" Identifiers.
syn match hareDelimiter '::' display
syn match hareName '\<\h\w*\>' nextgroup=@harePostfix skipempty skipwhite transparent
# Delimiters.
syn match hareDelimiter '::'
" Labels.
syn match hareLabel ':\h\w*\>' display
# Labels.
syn match hareUserLabel ':\h\w*\>' contains=hareName
" Match `size` as a type unless it is followed by an open paren.
syn match hareType '\<size\>' display
syn match hareBuiltin '\<size\ze(' display
" 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
# Default highlighting {{{1
hi def link hareAnnotation PreProc
hi def link hareAnnotationParens hareAnnotation
hi def link hareAttribute PreProc
hi def link hareBoolean Boolean
hi def link hareBuiltin Operator
hi def link hareComment Comment
hi def link hareCommentCode hareComment
hi def link hareCommentRef SpecialComment
hi def link hareConditional Conditional
hi def link hareConstant Constant
hi def link hareDefine Define
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 hareFloat Float
hi def link hareFormat SpecialChar
hi def link hareFunction Function
hi def link hareInclude Include
hi def link hareKeyword Keyword
hi def link hareLabel Special
hi def link hareLabel Label
hi def link hareNumber Number
hi def link hareOperator Operator
hi def link harePointer hareStorageClass
hi def link hareRepeat Repeat
hi def link hareRune Character
hi def link hareSliceBounds harePointer
hi def link hareStorageClass StorageClass
hi def link hareString String
hi def link hareStructure Structure
hi def link hareTodo Todo
hi def link hareType Type
hi def link hareTypedef Typedef
hi def link hareUserLabel Identifier
" Highlight embedded haredoc references.
hi! def link haredocRefValid SpecialComment
" Highlight whitespace errors by default.
if get(g:, 'hare_space_error', 1)
hi def link hareSpaceError Error
# Optionally highlight symbolic operators.
if get(g:, 'hare_symbol_operators')
hi! def link hareSymbolOperator hareOperator
else
hi! def link hareSymbolOperator NONE
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.
" Language: Haredoc (Hare documentation format)
" Maintainer: Amelia Clarke <selene@perilune.dev>
" Last Change: 2024-05-10
" Upstream: https://git.sr.ht/~selene/hare.vim
vim9script
# Vim syntax file.
# Language: Haredoc (Hare documentation format)
# Maintainer: Amelia Clarke <selene@perilune.dev>
# Last Change: 2025 Aug 14
# Upstream: https://git.sr.ht/~sircmpwn/hare.vim
if exists('b:current_syntax')
finish
endif
let b:current_syntax = 'haredoc'
" Syntax {{{1
# Syntax {{{1
syn case match
syn iskeyword @,48-57,_
" Code samples.
syn region haredocCodeSample excludenl start='\t\zs' end='$' contains=@NoSpell display
# Embedded code samples.
syn region haredocCode start='\t\zs' end='$' contains=@NoSpell display
" References to other declarations and modules.
syn region haredocRef start='\[\[' end=']]' contains=haredocRefValid,@NoSpell display keepend oneline
syn match haredocRefValid '\v\[\[\h\w*%(::\h\w*)*%(::)?]]' contained contains=@NoSpell display
# References to other declarations and modules.
syn match haredocRef '\v\[\[\h\w*%(::\h\w*)*%(::)?]]' contains=@NoSpell display
" Miscellaneous.
# Miscellaneous.
syn keyword haredocTodo FIXME TODO XXX
" Default highlighting {{{1
hi def link haredocCodeSample Comment
hi def link haredocRef Error
hi def link haredocRefValid Special
# Default highlighting {{{1
hi def link haredocCode Comment
hi def link haredocRef Special
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()
filetype on
call assert_true(mkdir('foo/bar', 'pR'))
call writefile([], 'README', 'D')
@@ -1715,28 +1716,37 @@ func Test_haredoc_file()
call assert_notequal('haredoc', &filetype)
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
split README
call assert_notequal('haredoc', &filetype)
bwipe!
call writefile([], 'foo/quux.ha')
call writefile([], 'main.ha', 'D')
split README
call assert_equal('haredoc', &filetype)
bwipe!
call delete('foo/quux.ha')
call writefile([], 'foo/bar/baz.ha', 'D')
let g:filetype_haredoc = 0
split README
call assert_notequal('haredoc', &filetype)
bwipe!
let g:haredoc_search_depth = 2
split README
call assert_equal('haredoc', &filetype)
bwipe!
unlet g:filetype_haredoc
unlet g:haredoc_search_depth
filetype off
endfunc