diff --git a/runtime/autoload/dist/ft.vim b/runtime/autoload/dist/ft.vim index 4d39689cc0..b0609727e0 100644 --- a/runtime/autoload/dist/ft.vim +++ b/runtime/autoload/dist/ft.vim @@ -3,7 +3,7 @@ vim9script # Vim functions for file type detection # # Maintainer: The Vim Project -# Last Change: 2025 Sep 04 +# Last Change: 2025 Sep 08 # Former Maintainer: Bram Moolenaar # These functions are moved here from runtime/filetype.vim to make startup @@ -441,29 +441,29 @@ 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(':h', get(g:, 'haredoc_search_depth', 1)) - setf haredoc - endif + if IsHareModule(':h', get(g:, 'filetype_haredoc', 1)) + setf haredoc endif enddef diff --git a/runtime/autoload/hare.vim b/runtime/autoload/hare.vim index c4581fccf9..479b0f6812 100644 --- a/runtime/autoload/hare.vim +++ b/runtime/autoload/hare.vim @@ -1,26 +1,82 @@ -" Vim autoload file. -" Language: Hare -" Maintainer: Amelia Clarke -" 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 +# 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) - return '/usr/src/hare/stdlib,/usr/src/hare/third-party' +# Returns the value of HAREPATH, if it exists. Otherwise, returns a safe +# default. +export def GetPath(): string + var path: list + if !empty($HAREPATH) + path = split($HAREPATH, ':') + else + path = ParsePath() + if empty(path) + return '/usr/src/hare/stdlib,/usr/src/hare/third-party' + endif endif - return substitute($HAREPATH, ':', ',', 'g') -endfunction + 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('') =~ '^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 + 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 diff --git a/runtime/compiler/hare.vim b/runtime/compiler/hare.vim index 33edb3a281..88f36a9e20 100644 --- a/runtime/compiler/hare.vim +++ b/runtime/compiler/hare.vim @@ -1,29 +1,35 @@ -" Vim compiler file. -" Compiler: Hare -" Maintainer: Amelia Clarke -" 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 +# 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 diff --git a/runtime/doc/ft_hare.txt b/runtime/doc/ft_hare.txt index 937c5e0961..cf0b1f9357 100644 --- a/runtime/doc/ft_hare.txt +++ b/runtime/doc/ft_hare.txt @@ -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 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: diff --git a/runtime/doc/tags b/runtime/doc/tags index 489cb39893..c78a0d934f 100644 --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -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* diff --git a/runtime/ftplugin/hare.vim b/runtime/ftplugin/hare.vim index 6c61c818d1..eca1a78817 100644 --- a/runtime/ftplugin/hare.vim +++ b/runtime/ftplugin/hare.vim @@ -1,61 +1,52 @@ -" Vim filetype plugin. -" Language: Hare -" Maintainer: Amelia Clarke -" Last Updated: 2024 Oct 04 -" Upstream: https://git.sr.ht/~sircmpwn/hare.vim +vim9script + +# Vim filetype plugin. +# Language: Hare +# Maintainer: Amelia Clarke +# 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. - if get(g:, 'hare_space_error', 1) +# 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 + augroup END 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 diff --git a/runtime/ftplugin/haredoc.vim b/runtime/ftplugin/haredoc.vim index 69030b47ba..ca66b06638 100644 --- a/runtime/ftplugin/haredoc.vim +++ b/runtime/ftplugin/haredoc.vim @@ -1,44 +1,51 @@ -" Vim filetype plugin. -" Language: Haredoc (Hare documentation format) -" Maintainer: Amelia Clarke -" 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 +# 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 diff --git a/runtime/indent/hare.vim b/runtime/indent/hare.vim index 1b51d1e80a..84496348a1 100644 --- a/runtime/indent/hare.vim +++ b/runtime/indent/hare.vim @@ -1,146 +1,340 @@ -" Vim indent file -" Language: Hare -" Maintainer: Amelia Clarke -" Last Change: 2024-04-14 -" Upstream: https://git.sr.ht/~sircmpwn/hare.vim +vim9script + +# Vim indent file. +# Language: Hare +# Maintainer: Amelia Clarke +# 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 -> (not inserted) -" o -> 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 -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() + # 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 - " 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() - 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() + # 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 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() + # If the previous line started the block, use the same indent. + if pline =~ '{$' + return pindent endif - " Otherwise, do a normal cindent. - return l:indent + # If the current line contains a `:` that is not part of `::`, use the + # 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 - " Don't indent an extra shiftwidth for cases which span multiple lines. - if prevline =~# '\v\=\>\s*(//.*)?$' && prevline !~# '\v^\s*case\W' - return indent(prevlnum) + # 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 + + # 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 - " 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) + # If the previous line ended with `=`, indent. + if pline =~ '=$' + return pindent + shiftwidth() 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() + # If the previous line opened an array literal, indent. + if pline =~ '[$' + return pindent + shiftwidth() endif - " If everything above is false, do a normal cindent. - return l:indent -endfunction + # If the previous line started a binding expression, indent. + if pline =~ '\v<%(const|def|let|type)$' + return pindent + shiftwidth() + endif -let &cpo = s:cpo_save -unlet s:cpo_save + # 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 -" 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 + 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 diff --git a/runtime/syntax/hare.vim b/runtime/syntax/hare.vim index 4c7ae92486..992b7b9059 100644 --- a/runtime/syntax/hare.vim +++ b/runtime/syntax/hare.vim @@ -1,157 +1,268 @@ -" Vim syntax file. -" Language: Hare -" Maintainer: Amelia Clarke -" Last Change: 2024-05-10 -" Upstream: https://git.sr.ht/~sircmpwn/hare.vim +vim9script + +# Vim syntax file. +# Language: Hare +# Maintainer: Amelia Clarke +# 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 '\' +syn match hareBuiltin '\]=\?' +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 '\' display -syn match hareBuiltin '\'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 diff --git a/runtime/syntax/haredoc.vim b/runtime/syntax/haredoc.vim index 09c99c1d56..adf15bc3df 100644 --- a/runtime/syntax/haredoc.vim +++ b/runtime/syntax/haredoc.vim @@ -1,32 +1,43 @@ -" Vim syntax file. -" Language: Haredoc (Hare documentation format) -" Maintainer: Amelia Clarke -" 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 +# 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 diff --git a/src/testdir/test_filetype.vim b/src/testdir/test_filetype.vim index d91d1bafe0..8452739b3e 100644 --- a/src/testdir/test_filetype.vim +++ b/src/testdir/test_filetype.vim @@ -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