mirror of
https://github.com/macvim-dev/macvim.git
synced 2026-02-01 11:33:15 +01:00
This reverts commits: -6b652a7850-2f689d5abd-a025a46d41Several email addresses that are known to be valid caused bounces due to an issue with my email setup. The previous commits incorrectly marked these addresses as invalid. So revert the whole thing again. Signed-off-by: Christian Brabandt <cb@256bit.org>
341 lines
10 KiB
VimL
341 lines
10 KiB
VimL
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
|
|
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<'
|
|
|
|
# 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 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
|
|
|
|
# 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 the block, use the same indent.
|
|
if pline =~ '{$'
|
|
return pindent
|
|
endif
|
|
|
|
# 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
|
|
|
|
# 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
|
|
|
|
# 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
|