diff options
63 files changed, 1240 insertions, 688 deletions
diff --git a/README_VIM9.md b/README_VIM9.md index b3ca49f091..c9ce73a2d1 100644 --- a/README_VIM9.md +++ b/README_VIM9.md @@ -2,18 +2,17 @@ # What is Vim9? -This is an experimental side of [Vim](https://github.com/vim/vim). -It explores ways of making Vim script faster and better. +This is a new syntax for Vim script that was introduced with Vim 9.0. +It intends making Vim script faster and better. -WARNING: The Vim9 script features are still under development, anything can -break! # Why Vim9? ## 1. FASTER VIM SCRIPT The third item on the poll results of 2018, after popup windows and text -properties, is faster Vim script. So how do we do that? +properties, both of which have been implemented, is faster Vim script. +So how do we do that? I have been throwing some ideas around, and soon came to the conclusion that the current way functions are called and executed, with @@ -53,7 +52,7 @@ we can gain, and also that Vim script can be faster than builtin interfaces. LuaJit is much faster at Lua-only instructions. In practice the script would -not do something useless as counting but change the text. For example, +not do something useless counting, but change the text. For example, reindent all the lines: ``` vim diff --git a/runtime/autoload/dist/vimindent.vim b/runtime/autoload/dist/vimindent.vim index 898f22f65c..8d86543cb4 100644 --- a/runtime/autoload/dist/vimindent.vim +++ b/runtime/autoload/dist/vimindent.vim @@ -2,12 +2,12 @@ vim9script # Language: Vim script # Maintainer: github user lacygoill -# Last Change: 2023 Jan 03 +# Last Change: 2023 Feb 01 # NOTE: Whenever you change the code, make sure the tests are still passing: # # $ cd runtime/indent/ -# $ make clean; make test || vimdiff testdir/vim.{fail,ok} +# $ make clean; make test || vimdiff testdir/vim.{ok,fail} # Config {{{1 @@ -112,6 +112,10 @@ const DICT_KEY: string = '^\s*\%(' .. '\)' .. ':\%(\s\|$\)' +# NOT_A_DICT_KEY {{{3 + +const NOT_A_DICT_KEY: string = ':\@!' + # END_OF_COMMAND {{{3 const END_OF_COMMAND: string = $'\s*\%($\|||\@!\|{INLINE_COMMENT}\)' @@ -144,19 +148,43 @@ const HEREDOC_OPERATOR: string = '\s=<<\s\@=\%(\s\+\%(trim\|eval\)\)\{,2}' # # But sometimes, it can be too costly and cause `E363` to be given. const PATTERN_DELIMITER: string = '[-+*/%]\%(=\s\)\@!' - -# QUOTE {{{3 - -const QUOTE: string = '["'']' # }}}2 # Syntaxes {{{2 -# ASSIGNS_HEREDOC {{{3 - -const ASSIGNS_HEREDOC: string = $'^\%({COMMENT}\)\@!.*\%({HEREDOC_OPERATOR}\)\s\+\zs[A-Z]\+{END_OF_LINE}' - -# CD_COMMAND {{{3 - -const CD_COMMAND: string = $'\<[lt]\=cd!\=\s\+-{END_OF_COMMAND}' +# BLOCKS {{{3 + +const BLOCKS: list<list<string>> = [ + ['if', 'el\%[se]', 'elseif\=', 'en\%[dif]'], + ['for', 'endfor\='], + ['wh\%[ile]', 'endw\%[hile]'], + ['try', 'cat\%[ch]', 'fina\|finally\=', 'endt\%[ry]'], + ['def', 'enddef'], + ['fu\%[nction](\@!', 'endf\%[unction]'], + ['class', 'endclass'], + ['interface', 'endinterface'], + ['enum', 'endenum'], + ['aug\%[roup]\%(\s\+[eE][nN][dD]\)\@!\s\+\S\+', 'aug\%[roup]\s\+[eE][nN][dD]'], +] + +# MODIFIERS {{{3 + +# some keywords can be prefixed by modifiers (e.g. `def` can be prefixed by `export`) +const MODIFIERS: dict<string> = { + def: ['export', 'static'], + class: ['export', 'abstract', 'export abstract'], + interface: ['export'], +} +# ... +# class: ['export', 'abstract', 'export abstract'], +# ... +# → +# ... +# class: '\%(export\|abstract\|export\s\+abstract\)\s\+', +# ... +->map((_, mods: list<string>): string => + '\%(' .. mods + ->join('\|') + ->substitute('\s\+', '\\s\\+', 'g') + .. '\)' .. '\s\+') # HIGHER_ORDER_COMMAND {{{3 @@ -174,58 +202,102 @@ patterns =<< trim eval END g\%[lobal]!\={PATTERN_DELIMITER}.* v\%[global]!\={PATTERN_DELIMITER}.* END -const HIGHER_ORDER_COMMAND: string = $'\%(^\|{BAR_SEPARATION}\)\s*\<\%(' .. patterns->join('\|') .. '\):\@!' -# MAPPING_COMMAND {{{3 +const HIGHER_ORDER_COMMAND: string = $'\%(^\|{BAR_SEPARATION}\)\s*\<\%({patterns->join('\|')}\){NOT_A_DICT_KEY}' -const MAPPING_COMMAND: string = '\%(\<sil\%[ent]!\=\s\+\)\=\<[nvxsoilct]\=\%(nore\|un\)map!\=\s' +# START_MIDDLE_END {{{3 -# NORMAL_COMMAND {{{3 +# Let's derive this constant from `BLOCKS`: +# +# [['if', 'el\%[se]', 'elseif\=', 'en\%[dif]'], +# ['for', 'endfor\='], +# ..., +# [...]] +# → +# { +# 'for': ['for', '', 'endfor\='], +# 'endfor': ['for', '', 'endfor\='], +# 'if': ['if', 'el\%[se]\|elseif\=', 'en\%[dif]'], +# 'else': ['if', 'el\%[se]\|elseif\=', 'en\%[dif]'], +# 'elseif': ['if', 'el\%[se]\|elseif\=', 'en\%[dif]'], +# 'endif': ['if', 'el\%[se]\|elseif\=', 'en\%[dif]'], +# ... +# } +var START_MIDDLE_END: dict<list<string>> + +def Unshorten(kwd: string): string + return BlockStartKeyword(kwd) +enddef -const NORMAL_COMMAND: string = '\<norm\%[al]!\=\s*\S\+$' +def BlockStartKeyword(line: string): string + var kwd: string = line->matchstr('\l\+') + return fullcommand(kwd, false) +enddef -# PLUS_MINUS_COMMAND {{{3 +{ + for kwds: list<string> in BLOCKS + var [start: string, middle: string, end: string] = [kwds[0], '', kwds[-1]] + if MODIFIERS->has_key(start->Unshorten()) + start = $'\%({MODIFIERS[start]}\)\={start}' + endif + if kwds->len() > 2 + middle = kwds[1 : -2]->join('\|') + endif + for kwd: string in kwds + START_MIDDLE_END->extend({[kwd->Unshorten()]: [start, middle, end]}) + endfor + endfor +} -# In legacy, the `:+` and `:-` commands are not required to be preceded by a colon. -# As a result, when `+` or `-` is alone on a line, there is ambiguity. -# It might be an operator or a command. -# To not break the indentation in legacy scripts, we might need to consider such -# lines as commands. -const PLUS_MINUS_COMMAND: string = '^\s*[+-]\s*$' +START_MIDDLE_END = START_MIDDLE_END + ->map((_, kwds: list<string>) => + kwds->map((_, kwd: string) => kwd == '' + ? '' + : $'\%(^\|{BAR_SEPARATION}\|\<sil\%[ent]\|{HIGHER_ORDER_COMMAND}\)\s*' + .. $'\<\%({kwd}\)\>\%(\s*{OPERATOR}\)\@!')) + +lockvar! START_MIDDLE_END # ENDS_BLOCK {{{3 const ENDS_BLOCK: string = '^\s*\%(' - .. 'en\%[dif]' - .. '\|' .. 'endfor\=' - .. '\|' .. 'endw\%[hile]' - .. '\|' .. 'endt\%[ry]' - .. '\|' .. 'enddef' - .. '\|' .. 'endclass' - .. '\|' .. 'endf\%[unction]' - .. '\|' .. 'aug\%[roup]\s\+[eE][nN][dD]' + .. BLOCKS + ->copy() + ->map((_, kwds: list<string>): string => kwds[-1]) + ->join('\|') .. '\|' .. CLOSING_BRACKET .. $'\){END_OF_COMMAND}' # ENDS_BLOCK_OR_CLAUSE {{{3 -patterns =<< trim END - en\%[dif] - el\%[se] - endfor\= - endclass - endw\%[hile] - endt\%[ry] - fina\|finally\= - enddef - endf\%[unction] - aug\%[roup]\s\+[eE][nN][dD] -END +patterns = BLOCKS + ->copy() + ->map((_, kwds: list<string>) => kwds[1 :]) + ->flattennew() + # `catch` and `elseif` need to be handled as special cases + ->filter((_, pat: string): bool => pat->Unshorten() !~ '^\%(catch\|elseif\)\>') const ENDS_BLOCK_OR_CLAUSE: string = '^\s*\%(' .. patterns->join('\|') .. $'\){END_OF_COMMAND}' .. $'\|^\s*cat\%[ch]\%(\s\+\({PATTERN_DELIMITER}\).*\1\)\={END_OF_COMMAND}' .. $'\|^\s*elseif\=\>\%({OPERATOR}\)\@!' +# STARTS_NAMED_BLOCK {{{3 + +patterns = [] +{ + for kwds: list<string> in BLOCKS + for kwd: string in kwds[0 : -2] + if MODIFIERS->has_key(kwd->Unshorten()) + patterns += [$'\%({MODIFIERS[kwd]}\)\={kwd}'] + else + patterns += [kwd] + endif + endfor + endfor +} + +const STARTS_NAMED_BLOCK: string = $'^\s*\%(sil\%[ent]\s\+\)\=\%({patterns->join('\|')}\)\>{NOT_A_DICT_KEY}' + # STARTS_CURLY_BLOCK {{{3 # TODO: `{` alone on a line is not necessarily the start of a block. @@ -238,73 +310,57 @@ const STARTS_CURLY_BLOCK: string = '\%(' .. '\|' .. $'^\%(\s*\|.*{BAR_SEPARATION}\s*\)\%(com\%[mand]\|au\%[tocmd]\).*\zs\s{{' .. '\)' .. END_OF_COMMAND -# STARTS_NAMED_BLOCK {{{3 - -# All of these will be used at the start of a line (or after a bar). -# NOTE: Don't replace `\%x28` with `(`.{{{ -# -# Otherwise, the paren would be unbalanced which might cause syntax highlighting -# issues much later in the code of the current script (sometimes, the syntax -# highlighting plugin fails to correctly recognize a heredoc which is far away -# and/or not displayed because inside a fold). -# }}} -patterns =<< trim END - if - el\%[se] - elseif\= - for - class - wh\%[ile] - try - cat\%[ch] - fina\|finally\= - fu\%[nction]\%x28\@! - \%(export\s\+\)\=def - aug\%[roup]\%(\s\+[eE][nN][dD]\)\@!\s\+\S\+ -END -const STARTS_NAMED_BLOCK: string = '^\s*\%(sil\%[ent]\s\+\)\=\%(' .. patterns->join('\|') .. '\)\>:\@!' - # STARTS_FUNCTION {{{3 -const STARTS_FUNCTION: string = '^\s*\%(export\s\+\)\=def\>:\@!' +const STARTS_FUNCTION: string = $'^\s*\%({MODIFIERS.def}\)\=def\>{NOT_A_DICT_KEY}' # ENDS_FUNCTION {{{3 -const ENDS_FUNCTION: string = $'^\s*enddef\>:\@!{END_OF_COMMAND}' +const ENDS_FUNCTION: string = $'^\s*enddef\>{END_OF_COMMAND}' -# START_MIDDLE_END {{{3 +# ASSIGNS_HEREDOC {{{3 + +const ASSIGNS_HEREDOC: string = $'^\%({COMMENT}\)\@!.*\%({HEREDOC_OPERATOR}\)\s\+\zs[A-Z]\+{END_OF_LINE}' -const START_MIDDLE_END: dict<list<string>> = { - if: ['if', 'el\%[se]\|elseif\=', 'en\%[dif]'], - else: ['if', 'el\%[se]\|elseif\=', 'en\%[dif]'], - elseif: ['if', 'el\%[se]\|elseif\=', 'en\%[dif]'], - endif: ['if', 'el\%[se]\|elseif\=', 'en\%[dif]'], - for: ['for', '', 'endfor\='], - endfor: ['for', '', 'endfor\='], - class: ['class', '', 'endclass'], - endclass: ['class', '', 'endclass'], - while: ['wh\%[ile]', '', 'endw\%[hile]'], - endwhile: ['wh\%[ile]', '', 'endw\%[hile]'], - try: ['try', 'cat\%[ch]\|fina\|finally\=', 'endt\%[ry]'], - catch: ['try', 'cat\%[ch]\|fina\|finally\=', 'endt\%[ry]'], - finally: ['try', 'cat\%[ch]\|fina\|finally\=', 'endt\%[ry]'], - endtry: ['try', 'cat\%[ch]\|fina\|finally\=', 'endt\%[ry]'], - def: ['\%(export\s\+\)\=def', '', 'enddef'], - enddef: ['\%(export\s\+\)\=def', '', 'enddef'], - function: ['fu\%[nction]', '', 'endf\%[unction]'], - endfunction: ['fu\%[nction]', '', 'endf\%[unction]'], - augroup: ['aug\%[roup]\%(\s\+[eE][nN][dD]\)\@!\s\+\S\+', '', 'aug\%[roup]\s\+[eE][nN][dD]'], -}->map((_, kwds: list<string>) => - kwds->map((_, kwd: string) => kwd == '' - ? '' - : $'\%(^\|{BAR_SEPARATION}\|\<sil\%[ent]\|{HIGHER_ORDER_COMMAND}\)\s*' - .. $'\%({printf('\C\<\%%(%s\)\>:\@!\%%(\s*%s\)\@!', kwd, OPERATOR)}\)')) +# PLUS_MINUS_COMMAND {{{3 + +# In legacy, the `:+` and `:-` commands are not required to be preceded by a colon. +# As a result, when `+` or `-` is alone on a line, there is ambiguity. +# It might be an operator or a command. +# To not break the indentation in legacy scripts, we might need to consider such +# lines as commands. +const PLUS_MINUS_COMMAND: string = '^\s*[+-]\s*$' + +# TRICKY_COMMANDS {{{3 + +# Some commands are tricky because they accept an argument which can be +# conflated with an operator. Examples: +# +# argdelete * +# cd - +# normal! == +# nunmap <buffer> ( +# +# TODO: Other commands might accept operators as argument. Handle them too. +patterns =<< trim eval END + {'\'}<argd\%[elete]\s\+\*\s*$ + \<[lt]\=cd!\=\s\+-\s*$ + \<norm\%[al]!\=\s*\S\+$ + \%(\<sil\%[ent]!\=\s\+\)\=\<[nvxsoilct]\=\%(nore\|un\)map!\=\s + {PLUS_MINUS_COMMAND} +END + +const TRICKY_COMMANDS: string = patterns->join('\|') # }}}2 # EOL {{{2 # OPENING_BRACKET_AT_EOL {{{3 const OPENING_BRACKET_AT_EOL: string = OPENING_BRACKET .. END_OF_VIM9_LINE +# CLOSING_BRACKET_AT_EOL {{{3 + +const CLOSING_BRACKET_AT_EOL: string = CLOSING_BRACKET .. END_OF_VIM9_LINE + # COMMA_AT_EOL {{{3 const COMMA_AT_EOL: string = $',{END_OF_VIM9_LINE}' @@ -392,6 +448,7 @@ export def Expr(lnum = v:lnum): number # {{{2 endif if line_A->AtStartOf('FuncHeader') + && !IsInInterface() line_A.lnum->CacheFuncHeader() elseif line_A.lnum->IsInside('FuncHeader') return b:vimindent.startindent + 2 * shiftwidth() @@ -430,6 +487,7 @@ export def Expr(lnum = v:lnum): number # {{{2 if line_A.text->ContinuesBelowBracketBlock(line_B, past_bracket_block) && line_A.text !~ CLOSING_BRACKET_AT_SOL return past_bracket_block.startindent + + (past_bracket_block.startline =~ STARTS_NAMED_BLOCK ? 2 * shiftwidth() : 0) endif # Problem: If we press `==` on the line right below the start of a multiline @@ -438,6 +496,18 @@ export def Expr(lnum = v:lnum): number # {{{2 if line_B->EndsWithLambdaArrow() return Indent(line_B.lnum) + shiftwidth() + IndentMoreInBracketBlock() endif + # FIXME: Similar issue here: + # + # var x = [] + # ->filter((_, _) => + # true) + # ->items() + # + # Press `==` on last line. + # Expected: The `->items()` line is indented like `->filter(...)`. + # Actual: It's indented like `true)`. + # Is it worth fixing? `=ip` gives the correct indentation, because then the + # cache is used. # Don't move this block before the heredoc one.{{{ # @@ -536,8 +606,13 @@ def Offset( # {{{2 line_B: dict<any>, ): number + if line_B->AtStartOf('FuncHeader') + && IsInInterface() + return 0 + # increase indentation inside a block - if line_B.text =~ STARTS_NAMED_BLOCK || line_B->EndsWithCurlyBlock() + elseif line_B.text =~ STARTS_NAMED_BLOCK + || line_B->EndsWithCurlyBlock() # But don't indent if the line starting the block also closes it. if line_B->AlsoClosesBlock() return 0 @@ -807,11 +882,6 @@ def Indent(lnum: number): number # {{{3 return indent(lnum) enddef -def BlockStartKeyword(line: string): string # {{{3 - var kwd: string = line->matchstr('\l\+') - return fullcommand(kwd, false) -enddef - def MatchingOpenBracket(line: dict<any>): number # {{{3 var end: string = line.text->matchstr(CLOSING_BRACKET) var start: string = {']': '[', '}': '{', ')': '('}[end] @@ -908,7 +978,8 @@ def SearchPair( # {{{3 if end == '[' || end == ']' e = e->escape('[]') endif - return searchpair(s, middle, e, flags, (): bool => InCommentOrString(), stopline, TIMEOUT) + return searchpair('\C' .. s, (middle == '' ? '' : '\C' .. middle), '\C' .. e, + flags, (): bool => InCommentOrString(), stopline, TIMEOUT) enddef def SearchPairStart( # {{{3 @@ -1016,6 +1087,10 @@ def IsInThisBlock(line_A: dict<any>, lnum: number): bool # {{{3 return line_A.lnum <= end enddef +def IsInInterface(): bool # {{{3 + return SearchPair('interface', '', 'endinterface', 'nW') > 0 +enddef + def IsFirstLineOfCommand(line_1: dict<any>, line_2: dict<any>): bool # {{{3 if line_1.text->Is_IN_KeywordForLoop(line_2.text) return false @@ -1096,6 +1171,10 @@ def EndsWithOpeningBracket(line: dict<any>): bool # {{{3 return NonCommentedMatch(line, OPENING_BRACKET_AT_EOL) enddef +def EndsWithClosingBracket(line: dict<any>): bool # {{{3 + return NonCommentedMatch(line, CLOSING_BRACKET_AT_EOL) +enddef + def NonCommentedMatch(line: dict<any>, pat: string): bool # {{{3 # Could happen if there is no code above us, and we're not on the 1st line. # In that case, `PrevCodeLine()` returns `{lnum: 0, line: ''}`. @@ -1103,16 +1182,6 @@ def NonCommentedMatch(line: dict<any>, pat: string): bool # {{{3 return false endif - if line.text =~ PLUS_MINUS_COMMAND - return false - endif - - # In `argdelete *`, `*` is not a multiplication operator. - # TODO: Other commands can accept `*` as an argument. Handle them too. - if line.text =~ '\<argd\%[elete]\s\+\*\s*$' - return false - endif - # Technically, that's wrong. A line might start with a range and end with a # line continuation symbol. But it's unlikely. And it's useful to assume the # opposite because it prevents us from conflating a mark with an operator or @@ -1179,24 +1248,7 @@ def NonCommentedMatch(line: dict<any>, pat: string): bool # {{{3 return false endif - # `:help cd-` - if line.text =~ CD_COMMAND - return false - endif - - # At the end of a mapping, any character might appear; e.g. a paren: - # - # nunmap <buffer> ( - # - # Don't conflate this with a line continuation symbol. - if line.text =~ MAPPING_COMMAND - return false - endif - - # not a comparison operator - # vv - # normal! == - if line.text =~ NORMAL_COMMAND + if line.text =~ TRICKY_COMMANDS return false endif diff --git a/runtime/autoload/tohtml.vim b/runtime/autoload/tohtml.vim index 270891abcf..1086347473 100644 --- a/runtime/autoload/tohtml.vim +++ b/runtime/autoload/tohtml.vim @@ -1,6 +1,6 @@ " Vim autoload file for the tohtml plugin. " Maintainer: Ben Fritz <fritzophrenic@gmail.com> -" Last Change: 2019 Aug 16 +" Last Change: 2023 Jan 01 " " Additional contributors: " @@ -351,63 +351,65 @@ func! tohtml#Diff2HTML(win_list, buf_list) "{{{ let s:old_magic = &magic set magic - if s:settings.use_xhtml - if s:settings.encoding != "" - let xml_line = "<?xml version=\"1.0\" encoding=\"" . s:settings.encoding . "\"?>" - else - let xml_line = "<?xml version=\"1.0\"?>" + let html = [] + if !s:settings.no_doc + if s:settings.use_xhtml + if s:settings.encoding != "" + let xml_line = "<?xml version=\"1.0\" encoding=\"" . s:settings.encoding . "\"?>" + else + let xml_line = "<?xml version=\"1.0\"?>" + endif + let tag_close = ' />' endif - let tag_close = ' />' - endif - let style = [s:settings.use_xhtml ? "" : '-->'] - let body_line = '' + let style = [s:settings.use_xhtml ? "" : '-->'] + let body_line = '' - let html = [] - let s:html5 = 0 - if s:settings.use_xhtml - call add(html, xml_line) - endif - if s:settings.use_xhtml - call add(html, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">") - call add(html, '<html xmlns="http://www.w3.org/1999/xhtml">') - elseif s:settings.use_css && !s:settings.no_pre - call add(html, "<!DOCTYPE html>") - call add(html, '<html>') - let s:html5 = 1 - else - call add(html, '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"') - call add(html, ' "http://www.w3.org/TR/html4/loose.dtd">') - call add(html, '<html>') - endif - call add(html, '<head>') - - " include encoding as close to the top as possible, but only if not already - " contained in XML information - if s:settings.encoding != "" && !s:settings.use_xhtml - if s:html5 - call add(html, '<meta charset="' . s:settings.encoding . '"' . tag_close) + let s:html5 = 0 + if s:settings.use_xhtml + call add(html, xml_line) + endif + if s:settings.use_xhtml + call add(html, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">") + call add(html, '<html xmlns="http://www.w3.org/1999/xhtml">') + elseif s:settings.use_css && !s:settings.no_pre + call add(html, "<!DOCTYPE html>") + call add(html, '<html>') + let s:html5 = 1 else - call add(html, "<meta http-equiv=\"content-type\" content=\"text/html; charset=" . s:settings.encoding . '"' . tag_close) + call add(html, '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"') + call add(html, ' "http://www.w3.org/TR/html4/loose.dtd">') + call add(html, '<html>') endif - endif + call add(html, '<head>') - call add(html, '<title>diff</title>') - call add(html, '<meta name="Generator" content="Vim/'.v:version/100.'.'.v:version%100.'"'.tag_close) - call add(html, '<meta name="plugin-version" content="'.g:loaded_2html_plugin.'"'.tag_close) - call add(html, '<meta name="settings" content="'. - \ join(filter(keys(s:settings),'s:settings[v:val]'),','). - \ ',prevent_copy='.s:settings.prevent_copy. - \ ',use_input_for_pc='.s:settings.use_input_for_pc. - \ '"'.tag_close) - call add(html, '<meta name="colorscheme" content="'. - \ (exists('g:colors_name') - \ |