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:
committed by
Christian Brabandt
parent
6bb16d2cee
commit
6d68508e62
@ -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
|
||||
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<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
|
||||
|
||||
Reference in New Issue
Block a user