Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
mp.vim321 linesDownload Raw Back to indent
1vim9script2 3# MetaPost indent file4# Language:           MetaPost5# Maintainer:         Nicola Vitacolonna <nvitacolonna@gmail.com>6# Former Maintainers: Eugene Minkovskii <emin@mccme.ru>7# Latest Revision:    2026 Jan 108 9if exists("b:did_indent")10  finish11endif12 13b:did_indent = 114 15setlocal indentexpr=g:MetaPostIndent()16setlocal indentkeys+==end,=else,=fi,=fill,0),0]17setlocal nolisp18setlocal nosmartindent19 20b:undo_indent = "setl indentexpr< indentkeys< lisp< smartindent<"21 22# Regexps {{{23# Expressions starting indented blocks24const MP_OPEN_TAG = [25  '\<if\>',26  '\<else\%[if]\>',27  '\<for\%(\|ever\|suffixes\)\>',28  '\<begingroup\>',29  '\<\%(\|var\|primary\|secondary\|tertiary\)def\>',30  '^\s*\<begin\%(fig\|graph\|glyph\|char\|logochar\)\>',31  '[([{]',32  ]->extend(get(g:, "mp_open_tag", []))->join('\|')33 34# Expressions ending indented blocks35const MP_CLOSE_TAG = [36  '\<fi\>',37  '\<else\%[if]\>',38  '\<end\%(\|for\|group\|def\|fig\|char\|glyph\|graph\)\>',39  '[)\]}]'40  ]->extend(get(g:, "mp_close_tag", []))->join('\|')41 42# Statements that may span multiple lines and are ended by a semicolon. To43# keep this list short, statements that are unlikely to be very long or are44# not very common (e.g., keywords like `interim` or `showtoken`) are not45# included.46#47# The regex for assignments and equations (the last branch) is tricky, because48# it must not match things like `for i :=`, `if a=b`, `def...=`, etc... It is49# not perfect, but it works reasonably well.50const MP_STATEMENT = [51  '\<\%(\|un\|cut\)draw\%(dot\)\=\>',52  '\<\%(\|un\)fill\%[draw]\>',53  '\<draw\%(dbl\)\=arrow\>',54  '\<clip\>',55  '\<addto\>',56  '\<save\>',57  '\<setbounds\>',58  '\<message\>',59  '\<errmessage\>',60  '\<errhelp\>',61  '\<fontmapline\>',62  '\<pickup\>',63  '\<show\>',64  '\<special\>',65  '\<write\>',66  '\%(^\|;\)\%([^;=]*\%(' .. MP_OPEN_TAG .. '\)\)\@!.\{-}:\==',67  ]->join('\|')68 69# A line ends with zero or more spaces, possibly followed by a comment.70const EOL = '\s*\%($\|%\)'71# }}}72 73# Auxiliary functions {{{74# Returns true if (0-based) position immediately preceding `pos` in `line` is75# inside a string or a comment; returns false otherwise.76 77# This is the function that is called more often when indenting, so it is78# critical that it is efficient. The method we use is significantly faster79# than using syntax attributes, and more general (it does not require80# syntax_items). It is also faster than using a single regex matching an even81# number of quotes. It helps that MetaPost strings cannot span more than one82# line and cannot contain escaped quotes.83def IsCommentOrString(line: string, pos: number): bool84  var in_string = 085  var q = stridx(line, '"')86  var c = stridx(line, '%')87 88  while q >= 0 && q < pos89    if c >= 0 && c < q90      if in_string # Find next percent symbol91        c = stridx(line, '%', q + 1)92      else # Inside comment93        return true94      endif95    endif96    in_string = 1 - in_string97    q = stridx(line, '"', q + 1) # Find next quote98  endwhile99 100  return in_string || (c >= 0 && c <= pos)101enddef102 103# Find the first non-comment non-blank line before the given line.104def PrevNonBlankNonComment(lnum: number): number105  var nr = prevnonblank(lnum - 1)106  while getline(nr) =~# '^\s*%'107    nr = prevnonblank(nr - 1)108  endwhile109  return nr110enddef111 112# Returns true if the last tag appearing in the line is an open tag; returns113# false otherwise.114def LastTagIsOpen(line: string): bool115  var o = LastValidMatchEnd(line, MP_OPEN_TAG, 0)116  if o == - 1117    return false118  endif119  return LastValidMatchEnd(line, MP_CLOSE_TAG, o) < 0120enddef121 122# A simple, efficient and quite effective heuristics is used to test whether123# a line should cause the next line to be indented: count the "opening tags"124# (if, for, def, ...) in the line, count the "closing tags" (endif, endfor,125# ...) in the line, and compute the difference. We call the result the126# "weight" of the line. If the weight is positive, then the next line should127# most likely be indented. Note that `else` and `elseif` are both opening and128# closing tags, so they "cancel out" in almost all cases, the only exception129# being a leading `else[if]`, which is counted as an opening tag, but not as130# a closing tag (so that, for instance, a line containing a single `else:`131# will have weight equal to one, not zero). We do not treat a trailing132# `else[if]` in any special way, because lines ending with an open tag are133# dealt with separately before this function is called (see MetaPostIndent()).134#135# Example:136#137#     forsuffixes $=a,b: if x.$ = y.$ : draw else: fill fi138#       % This line will be indented because |{forsuffixes,if,else}| > |{else,fi}| (3 > 2)139#     endfor140def Weight(line: string): number141  var o = 0142  var i = ValidMatchEnd(line, MP_OPEN_TAG, 0)143  while i > 0144    o += 1145    i = ValidMatchEnd(line, MP_OPEN_TAG, i)146  endwhile147  var c = 0148  i = matchend(line, '^\s*\<else\%[if]\>')  # Skip a leading else[if]149  i = ValidMatchEnd(line, MP_CLOSE_TAG, i)150  while i > 0151    c += 1152    i = ValidMatchEnd(line, MP_CLOSE_TAG, i)153  endwhile154  return o - c155enddef156 157# Similar to matchend(), but skips strings and comments.158# line: a String159def ValidMatchEnd(line: string, pat: string, start: number): number160  var i = matchend(line, pat, start)161  while i > 0 && IsCommentOrString(line, i)162    i = matchend(line, pat, i)163  endwhile164  return i165enddef166 167# Like s:ValidMatchEnd(), but returns the end position of the last (i.e.,168# rightmost) match.169def LastValidMatchEnd(line: string, pat: string, start: number): number170  var last_found = -1171  var i = matchend(line, pat, start)172  while i > 0173    if !IsCommentOrString(line, i)174      last_found = i175    endif176    i = matchend(line, pat, i)177  endwhile178  return last_found179enddef180 181def DecreaseIndentOnClosingTag(curr_indent: number): number182  var cur_text = getline(v:lnum)183  if cur_text =~# '^\s*\%(' .. MP_CLOSE_TAG .. '\)'184    return max([curr_indent - shiftwidth(), 0])185  endif186  return curr_indent187enddef188# }}}189 190# Main function {{{191def g:MetaPostIndent(): number192  # Do not touch indentation inside verbatimtex/btex.. etex blocks.193  if synIDattr(synID(v:lnum, 1, 1), "name") =~# '^mpTeXinsert$\|^tex\|^Delimiter'194    return -1195  endif196 197  # At the start of a MetaPost block inside ConTeXt, do not touch indentation198  if synIDattr(synID(prevnonblank(v:lnum - 1), 1, 1), "name") == "contextBlockDelim"199    return -1200  endif201 202  var lnum = PrevNonBlankNonComment(v:lnum)203 204  # At the start of the file use zero indent.205  if lnum == 0206    return 0207  endif208 209  var prev_text = getline(lnum)210 211  # Every rule of indentation in MetaPost is very subjective. We might get212  # creative, but things get murky very soon (there are too many corner213  # cases). So, we provide a means for the user to decide what to do when this214  # script doesn't get it. We use a simple idea: use '%>', '%<', '%=', and215  # '%!', to explicitly control indentation. The '<' and '>' symbols may be216  # repeated many times (e.g., '%>>' will cause the next line to be indented217  # twice).218  #219  # User-defined overrides take precedence over anything else.220  var j = match(prev_text, '%[<>=!]')221  if j > 0222    var i = strlen(matchstr(prev_text, '%>\+', j)) - 1223    if i > 0224      return indent(lnum) + i * shiftwidth()225    endif226 227    i = strlen(matchstr(prev_text, '%<\+', j)) - 1228    if i > 0229      return max([indent(lnum) - i * shiftwidth(), 0])230    endif231 232    if match(prev_text, '%=', j) > -1233      return indent(lnum)234    endif235 236    if match(prev_text, '%!', j) > -1237      return -1238    endif239  endif240 241  # If the reference line ends with an open tag, indent.242  #243  # Example:244  #245  # if c:246  #     0247  # else:248  #     1249  # fi if c2: % Note that this line has weight equal to zero.250  #     ...   % This line will be indented251  if LastTagIsOpen(prev_text)252    return DecreaseIndentOnClosingTag(indent(lnum) + shiftwidth())253  endif254 255  # Lines with a positive weight are unbalanced and should likely be indented.256  #257  # Example:258  #259  # def f = enddef for i = 1 upto 5: if x[i] > 0: 1 else: 2 fi260  #     ... % This line will be indented (because of the unterminated `for`)261  if Weight(prev_text) > 0262    return DecreaseIndentOnClosingTag(indent(lnum) + shiftwidth())263  endif264 265  # Unterminated statements cause indentation to kick in.266  #267  # Example:268  #269  # draw unitsquare270  #     withcolor black; % This line is indented because of `draw`.271  # x := a + b + c272  #     + d + e;         % This line is indented because of `:=`.273  #274  var i = LastValidMatchEnd(prev_text, MP_STATEMENT, 0)275  if i >= 0 # Does the line contain a statement?276    if ValidMatchEnd(prev_text, ';', i) < 0 # Is the statement unterminated?277      return indent(lnum) + shiftwidth()278    else279      return DecreaseIndentOnClosingTag(indent(lnum))280    endif281  endif282 283  # Deal with the special case of a statement spanning multiple lines. If the284  # current reference line L ends with a semicolon, search backwards for285  # another semicolon or a statement keyword. If the latter is found first,286  # its line is used as the reference line for indenting the current line287  # instead of L.288  #289  #  Example:290  #291  #  if cond:292  #    draw if a: z0 else: z1 fi293  #        shifted S294  #        scaled T;      % L295  #296  #    for i = 1 upto 3:  % <-- Current line: this gets the same indent as `draw ...`297  #298  # NOTE: we get here only if L does not contain a statement (among those299  # listed in g:MP_STATEMENT).300  if ValidMatchEnd(prev_text, ';' .. EOL, 0) >= 0 # L ends with a semicolon301    var stm_lnum = PrevNonBlankNonComment(lnum)302    while stm_lnum > 0303      prev_text = getline(stm_lnum)304      var sc_pos = LastValidMatchEnd(prev_text, ';', 0)305      var stm_pos = ValidMatchEnd(prev_text, MP_STATEMENT, sc_pos)306      if stm_pos > sc_pos307        lnum = stm_lnum308        break309      elseif sc_pos > stm_pos310        break311      endif312      stm_lnum = PrevNonBlankNonComment(stm_lnum)313    endwhile314  endif315 316  return DecreaseIndentOnClosingTag(indent(lnum))317enddef318# }}}319 320# vim: sw=2 fdm=marker321 
codekingpro/portable-devtools · Team Ai