From 0cd0b7f8942a42bf8cb24affb18ac5bedae5aa48 Mon Sep 17 00:00:00 2001 From: Adam Stankiewicz Date: Mon, 4 Mar 2019 10:09:33 +0100 Subject: [PATCH] Add reason support, closes #266 --- README.md | 3 +- after/syntax/reason.vim | 4 + build | 1 + ftdetect/polyglot.vim | 11 ++ indent/reason.vim | 221 ++++++++++++++++++++++++++++++++++ syntax/merlin.vim | 17 +++ syntax/reason.vim | 254 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 510 insertions(+), 1 deletion(-) create mode 100644 after/syntax/reason.vim create mode 100644 indent/reason.vim create mode 100644 syntax/merlin.vim create mode 100644 syntax/reason.vim diff --git a/README.md b/README.md index 13295f2..dffb5f9 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A collection of language packs for Vim. > One to rule them all, one to find them, one to bring them all and in the darkness bind them. - It **won't affect your startup time**, as scripts are loaded only on demand\*. -- It **installs and updates 100+ times faster** than the 120 packages it consists of. +- It **installs and updates 100+ times faster** than the 121 packages it consists of. - Solid syntax and indentation support (other features skipped). Only the best language packs. - All unnecessary files are ignored (like enormous documentation from php support). - No support for esoteric languages, only most popular ones (modern too, like `slim`). @@ -132,6 +132,7 @@ If you need full functionality of any plugin, please use it directly with your p - [racket](https://github.com/wlangstroth/vim-racket) (syntax, indent, autoload, ftplugin) - [ragel](https://github.com/jneen/ragel.vim) (syntax) - [raml](https://github.com/IN3D/vim-raml) (syntax, ftplugin) +- [reason](https://github.com/reasonml-editor/vim-reason-plus) (syntax, indent) - [rspec](https://github.com/sheerun/rspec.vim) (syntax) - [rst](https://github.com/marshallward/vim-restructuredtext) (syntax, autoload, ftplugin) - [ruby](https://github.com/vim-ruby/vim-ruby) (syntax, indent, compiler, autoload, ftplugin) diff --git a/after/syntax/reason.vim b/after/syntax/reason.vim new file mode 100644 index 0000000..14d5abf --- /dev/null +++ b/after/syntax/reason.vim @@ -0,0 +1,4 @@ +if exists('g:polyglot_disabled') && index(g:polyglot_disabled, 'reason') != -1 + finish +endif + diff --git a/build b/build index 398a8e5..712d39d 100755 --- a/build +++ b/build @@ -240,6 +240,7 @@ PACKS=" racket:wlangstroth/vim-racket ragel:jneen/ragel.vim raml:IN3D/vim-raml + reason:reasonml-editor/vim-reason-plus rspec:sheerun/rspec.vim rst:marshallward/vim-restructuredtext ruby:vim-ruby/vim-ruby diff --git a/ftdetect/polyglot.vim b/ftdetect/polyglot.vim index ef4d4d0..b91c235 100644 --- a/ftdetect/polyglot.vim +++ b/ftdetect/polyglot.vim @@ -906,6 +906,17 @@ au BufRead,BufNewFile *.raml set ft=raml augroup end endif +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'reason') == -1 + augroup filetypedetect + " reason, from reason.vim in reasonml-editor/vim-reason-plus +" Copyright (c) 2015-present, Facebook, Inc. All rights reserved. + +au BufRead,BufNewFile *.re set filetype=reason +au BufRead,BufNewFile *.rei set filetype=reason +au BufNewFile,BufRead .merlin set ft=merlin + augroup end +endif + if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'ruby') == -1 augroup filetypedetect " ruby, from ruby.vim in vim-ruby/vim-ruby diff --git a/indent/reason.vim b/indent/reason.vim new file mode 100644 index 0000000..4901a4f --- /dev/null +++ b/indent/reason.vim @@ -0,0 +1,221 @@ +if exists('g:polyglot_disabled') && index(g:polyglot_disabled, 'reason') != -1 + finish +endif + +" Vim indent file +" Language: Reason (adapted from Rust) +" Author: Chris Morgan (Modifications by Jordan W) +" Portions Copyright (c) 2015-present, Facebook, Inc. All rights reserved + +" Only load this indent file when no other was loaded. +if exists("b:did_indent") + finish +endif +let b:did_indent = 1 + +" m1 is needed to make parens line up correct: +" +" let result = callSomething ( +" 1, +" 2 +" ); +" +" fN (See the docs) - additional indentation *w.r.t the "prevailing indent" only applies to blocks +" *not* in nested braces! We want it to match the blocks *inside* nested braces - and be zero, but +" the problem is that the "prevailing indent" not inside of braces is different than inside braces. +setlocal cinoptions=L0,(0,Ws,J1,j1,m1,{0,f0s +" cinkeys is ignored if indentexpr is activated so this setting is useless. See indentkeys below. +setlocal cinkeys=0{,0},0),!^F,o,O,0[,0] +" Don't think cinwords will actually do anything at all... never mind +setlocal cinwords=for,if,else,while,loop,impl,mod,unsafe,trait,struct,enum,fun,let,extern + +" Some preliminary settings +setlocal nolisp " Make sure lisp indenting doesn't supersede us +setlocal autoindent " indentexpr isn't much help otherwise +" Also do indentkeys, otherwise # gets shoved to column 0 :-/ +setlocal indentkeys=0{,0},0),!^F,o,O,0[,0] + + +setlocal indentexpr=GetReasonIndent(v:lnum) + +" Only define the function once. +if exists("*GetReasonIndent") + finish +endif + +" Come here when loading the script the first time. + +function! s:get_line_trimmed(lnum) + " Get the line and remove a trailing comment. + " Use syntax highlighting attributes when possible. + " NOTE: this is not accurate; /* */ or a line continuation could trick it + let line = getline(a:lnum) + let line_len = strlen(line) + if has('syntax_items') + " If the last character in the line is a comment, do a binary search for + " the start of the comment. synID() is slow, a linear search would take + " too long on a long line. + if synIDattr(synID(a:lnum, line_len, 1), "name") =~ 'Comment\|Todo' + let min = 1 + let max = line_len + while min < max + let col = (min + max) / 2 + if synIDattr(synID(a:lnum, col, 1), "name") =~ 'Comment\|Todo' + let max = col + else + let min = col + 1 + endif + endwhile + let line = strpart(line, 0, min - 1) + endif + return substitute(line, "\s*$", "", "") + else + " Sorry, this is not complete, nor fully correct (e.g. string "//"). + " Such is life. + return substitute(line, "\s*//.*$", "", "") + endif +endfunction + +function! s:is_string_comment(lnum, col) + if has('syntax_items') + for id in synstack(a:lnum, a:col) + let synname = synIDattr(id, "name") + if synname == "rustString" || synname =~ "^rustComment" + return 1 + endif + endfor + else + " without syntax, let's not even try + return 0 + endif +endfunction + +function GetReasonIndent(lnum) + + " Starting assumption: cindent (called at the end) will do it right + " normally. We just want to fix up a few cases. + + let line = getline(a:lnum) + + if has('syntax_items') + let synname = synIDattr(synID(a:lnum, 1, 1), "name") + if synname == "rustString" + " If the start of the line is in a string, don't change the indent + return -1 + elseif synname =~ '\(Comment\|Todo\)' + \ && line !~ '^\s*/\*' " not /* opening line + if synname =~ "CommentML" " multi-line + if line !~ '^\s*\*' && getline(a:lnum - 1) =~ '^\s*/\*' + " This is (hopefully) the line after a /*, and it has no + " leader, so the correct indentation is that of the + " previous line. + return GetReasonIndent(a:lnum - 1) + endif + endif + " If it's in a comment, let cindent take care of it now. This is + " for cases like "/*" where the next line should start " * ", not + " "* " as the code below would otherwise cause for module scope + " Fun fact: " /*\n*\n*/" takes two calls to get right! + return cindent(a:lnum) + endif + endif + + " cindent gets second and subsequent match patterns/struct members wrong, + " as it treats the comma as indicating an unfinished statement:: + " + " switch a { + " | b => c + " | d => e + " | f => g + " }; + + " Search backwards for the previous non-empty line. + let prevlinenum = prevnonblank(a:lnum - 1) + let prevline = s:get_line_trimmed(prevlinenum) + while prevlinenum > 1 && prevline !~ '[^[:blank:]]' + let prevlinenum = prevnonblank(prevlinenum - 1) + let prevline = s:get_line_trimmed(prevlinenum) + endwhile + + " Handle where clauses nicely: subsequent values should line up nicely. + if prevline[len(prevline) - 1] == "," + \ && prevline =~# '^\s*where\s' + return indent(prevlinenum) + 6 + endif + + if prevline =~ "\s*|.*[{(\[]$" + " \ && s:get_line_trimmed(a:lnum) !~ '^\s*[\[\]{}]' + " \ && prevline !~ '^\s*fun\s' + " \ && prevline !~ '([^()]\+,$' + " Oh ho! The previous line ended in a comma! I bet cindent will try to + " take this too far... For now, let's normally use the previous line's + " indent. + + " One case where this doesn't work out is where *this* line contains + " square or curly brackets; then we normally *do* want to be indenting + " further. + " + " Another case where we don't want to is one like a function + " definition with arguments spread over multiple lines: + " + " fun foo(baz: Baz, + " baz: Baz) // <-- cindent gets this right by itself + " + " Another case is similar to the previous, except calling a function + " instead of defining it, or any conditional expression that leaves + " an open paren: + " + " foo(baz, + " baz); + " + " if baz && (foo || + " bar) { + " + " There are probably other cases where we don't want to do this as + " well. Add them as needed. + return indent(prevlinenum) + &shiftwidth + endif + if prevline =~ "\s*|" + return indent(prevlinenum) + endif + + if !has("patch-7.4.355") + " cindent before 7.4.355 doesn't do the module scope well at all; e.g.:: + " + " static FOO : &'static [bool] = [ + " true, + " false, + " false, + " true, + " ]; + " + " uh oh, next statement is indented further! + + " Note that this does *not* apply the line continuation pattern properly; + " that's too hard to do correctly for my liking at present, so I'll just + " start with these two main cases (square brackets and not returning to + " column zero) + + call cursor(a:lnum, 1) + if searchpair('{\|(', '', '}\|)', 'nbW', + \ 's:is_string_comment(line("."), col("."))') == 0 + if searchpair('\[', '', '\]', 'nbW', + \ 's:is_string_comment(line("."), col("."))') == 0 + " Global scope, should be zero + return 0 + else + " At the module scope, inside square brackets only + "if getline(a:lnum)[0] == ']' || search('\[', '', '\]', 'nW') == a:lnum + if line =~ "^\\s*]" + " It's the closing line, dedent it + return 0 + else + return &shiftwidth + endif + endif + endif + endif + + " Fall back on cindent, which does it mostly right + return cindent(a:lnum) +endfunction diff --git a/syntax/merlin.vim b/syntax/merlin.vim new file mode 100644 index 0000000..9c2d2ee --- /dev/null +++ b/syntax/merlin.vim @@ -0,0 +1,17 @@ +if exists('g:polyglot_disabled') && index(g:polyglot_disabled, 'reason') != -1 + finish +endif + +" Vim syntax file for editing merlin project files +if exists("b:current_syntax") + finish +endif + +syn keyword merlinKeyword S B SUFFIX PKG REC EXT PRJ FLG CMI CMT +syn match merlinComment "\v#.*$" + +hi link merlinKeyword Keyword +hi link merlinComment Comment + +let b:current_syntax = "merlin" + diff --git a/syntax/reason.vim b/syntax/reason.vim new file mode 100644 index 0000000..6d47a01 --- /dev/null +++ b/syntax/reason.vim @@ -0,0 +1,254 @@ +if exists('g:polyglot_disabled') && index(g:polyglot_disabled, 'reason') != -1 + finish +endif + +" Vim syntax file +" Language: Reason (Forked from Rust) +" Maintainer: (Jordan - for Reason changes) Patrick Walton +" Maintainer: Ben Blum +" Maintainer: Chris Morgan +" Last Change: January 29, 2015 +" Portions Copyright (c) 2015-present, Facebook, Inc. All rights reserved. + +if version < 600 + syntax clear +elseif exists("b:current_syntax") + finish +endif + +" Syntax definitions {{{1 +" Basic keywords {{{2 +syn keyword rustConditional switch match if else for in when fun +syn keyword rustOperator as + +syn match rustAssert "\[^\.]"me=e-1 +" Polymorphic variants +syn match rustEnumVariant "`\w\(\w\|'\)*\>" + +syn match rustModPath "\<\u\w*\." + + +syn region rustBoxPlacement matchgroup=rustBoxPlacementParens start="(" end=")" contains=TOP contained +" Ideally we'd have syntax rules set up to match arbitrary expressions. Since +" we don't, we'll just define temporary contained rules to handle balancing +" delimiters. +syn region rustBoxPlacementBalance start="(" end=")" containedin=rustBoxPlacement transparent +syn region rustBoxPlacementBalance start="\[" end="\]" containedin=rustBoxPlacement transparent +" {} are handled by rustFoldBraces + + +syn region rustMacroRepeat matchgroup=rustMacroRepeatDelimiters start="$(" end=")" contains=TOP nextgroup=rustMacroRepeatCount +syn match rustMacroRepeatCount ".\?[*+]" contained +syn match rustMacroVariable "$\w\+" + +" Reserved (but not yet used) keywords {{{2 +syn keyword rustReservedKeyword alignof become do offsetof priv pure sizeof typeof unsized yield abstract virtual final override macro + +" Built-in types {{{2 +syn keyword rustType int float option list array unit ref bool string + +" Things from the libstd v1 prelude (src/libstd/prelude/v1.rs) {{{2 +" This section is just straight transformation of the contents of the prelude, +" to make it easy to update. + +" Reexported core operators {{{3 +syn keyword rustTrait Copy Send Sized Sync +syn keyword rustTrait Drop Fn FnMut FnOnce + +" Reexported functions {{{3 +" There’s no point in highlighting these; when one writes drop( or drop::< it +" gets the same highlighting anyway, and if someone writes `let drop = …;` we +" don’t really want *that* drop to be highlighted. +"syn keyword rustFunction drop + +" Reexported types and traits {{{3 +syn keyword rustTrait Box +syn keyword rustTrait ToOwned +syn keyword rustTrait Clone +syn keyword rustTrait PartialEq PartialOrd Eq Ord +syn keyword rustTrait AsRef AsMut Into From +syn keyword rustTrait Default +syn keyword rustTrait Iterator Extend IntoIterator +syn keyword rustTrait DoubleEndedIterator ExactSizeIterator +syn keyword rustEnum Option +syn keyword rustEnumVariant Some None +syn keyword rustEnum Result +syn keyword rustEnumVariant Ok Err +syn keyword rustTrait SliceConcatExt +syn keyword rustTrait String ToString +syn keyword rustTrait Vec + +" Other syntax {{{2 +syn keyword rustSelf self +syn keyword rustBoolean true false + +" This is merely a convention; note also the use of [A-Z], restricting it to +" latin identifiers rather than the full Unicode uppercase. I have not used +" [:upper:] as it depends upon 'noignorecase' +"syn match rustCapsIdent display "[A-Z]\w\(\w\)*" + +syn match rustOperator display "\%(+\|-\|/\|*\|=\|\^\|&\||\|!\|>\|<\|%\)=\?" +" This one isn't *quite* right, as we could have binary-& with a reference + +" This isn't actually correct; a closure with no arguments can be `|| { }`. +" Last, because the & in && isn't a sigil +syn match rustOperator display "&&\|||" +" This is rustArrowCharacter rather than rustArrow for the sake of matchparen, +" so it skips the ->; see http://stackoverflow.com/a/30309949 for details. +syn match rustArrowCharacter display "=>" + +syn match rustEscapeError display contained /\\./ +syn match rustEscape display contained /\\\([nrt0\\'"]\|x\x\{2}\)/ +syn match rustEscapeUnicode display contained /\\\(u\x\{4}\|U\x\{8}\)/ +syn match rustEscapeUnicode display contained /\\u{\x\{1,6}}/ +syn match rustStringContinuation display contained /\\\n\s*/ +syn region rustString start='{j|' end='|j}' contains=rustMacroVariable,@Spell +syn region rustString start=+b"+ skip=+\\\\\|\\"+ end=+"+ contains=rustEscape,rustEscapeError,rustStringContinuation +syn region rustString start=+"+ skip=+\\\\\|\\"+ end=+"+ contains=rustEscape,rustEscapeUnicode,rustEscapeError,rustStringContinuation,@Spell +syn region rustString start='b\?r\z(#*\)"' end='"\z1' contains=@Spell + +syn region rustAttribute start="#!\?\[" end="\]" contains=rustString,rustDerive +syn region rustDerive start="derive(" end=")" contained contains=rustDeriveTrait +" This list comes from src/libsyntax/ext/deriving/mod.rs +" Some are deprecated (Encodable, Decodable) or to be removed after a new snapshot (Show). +syn keyword rustDeriveTrait contained Clone Hash RustcEncodable RustcDecodable Encodable Decodable PartialEq Eq PartialOrd Ord Rand Show Debug Default FromPrimitive Send Sync Copy + +" Number literals +syn match rustDecNumber display "\<[0-9][0-9_]*\%([iu]\%(size\|8\|16\|32\|64\)\)\=" +syn match rustHexNumber display "\<0x[a-fA-F0-9_]\+\%([iu]\%(size\|8\|16\|32\|64\)\)\=" +syn match rustOctNumber display "\<0o[0-7_]\+\%([iu]\%(size\|8\|16\|32\|64\)\)\=" +syn match rustBinNumber display "\<0b[01_]\+\%([iu]\%(size\|8\|16\|32\|64\)\)\=" + +" Special case for numbers of the form "1." which are float literals, unless followed by +" an identifier, which makes them integer literals with a method call or field access, +" or by another ".", which makes them integer literals followed by the ".." token. +" (This must go first so the others take precedence.) +syn match rustFloat display "\<[0-9][0-9_]*\.\%([^[:cntrl:][:space:][:punct:][:digit:]]\|_\|\.\)\@!" +" To mark a number as a normal float, it must have at least one of the three things integral values don't have: +" a decimal point and more numbers; an exponent; and a type suffix. +syn match rustFloat display "\<[0-9][0-9_]*\%(\.[0-9][0-9_]*\)\%([eE][+-]\=[0-9_]\+\)\=\(f32\|f64\)\=" +syn match rustFloat display "\<[0-9][0-9_]*\%(\.[0-9][0-9_]*\)\=\%([eE][+-]\=[0-9_]\+\)\(f32\|f64\)\=" +syn match rustFloat display "\<[0-9][0-9_]*\%(\.[0-9][0-9_]*\)\=\%([eE][+-]\=[0-9_]\+\)\=\(f32\|f64\)" + +" For the benefit of delimitMate + +syn match rustCharacterInvalid display contained /b\?'\zs[\n\r\t']\ze'/ +" The groups negated here add up to 0-255 but nothing else (they do not seem to go beyond ASCII). +syn match rustCharacterInvalidUnicode display contained /b'\zs[^[:cntrl:][:graph:][:alnum:][:space:]]\ze'/ +syn match rustCharacter /b'\([^\\]\|\\\(.\|x\x\{2}\)\)'/ contains=rustEscape,rustEscapeError,rustCharacterInvalid,rustCharacterInvalidUnicode +syn match rustCharacter /'\([^\\]\|\\\(.\|x\x\{2}\|u\x\{4}\|U\x\{8}\|u{\x\{1,6}}\)\)'/ contains=rustEscape,rustEscapeUnicode,rustEscapeError,rustCharacterInvalid + +syn match rustShebang /\%^#![^[].*/ +syn region rustCommentLine start="//" end="$" contains=rustTodo,@Spell +syn region rustCommentLineDoc start="//\%(//\@!\|!\)" end="$" contains=rustTodo,@Spell +syn region rustCommentBlock matchgroup=rustCommentBlock start="/\*\%(!\|\*[*/]\@!\)\@!" end="\*/" contains=rustTodo,rustCommentBlockNest,@Spell +syn region rustCommentBlockDoc matchgroup=rustCommentBlockDoc start="/\*\%(!\|\*[*/]\@!\)" end="\*/" contains=rustTodo,rustCommentBlockDocNest,@Spell +syn region rustCommentBlockNest matchgroup=rustCommentBlock start="/\*" end="\*/" contains=rustTodo,rustCommentBlockNest,@Spell contained transparent +syn region rustCommentBlockDocNest matchgroup=rustCommentBlockDoc start="/\*" end="\*/" contains=rustTodo,rustCommentBlockDocNest,@Spell contained transparent +" FIXME: this is a really ugly and not fully correct implementation. Most +" importantly, a case like ``/* */*`` should have the final ``*`` not being in +" a comment, but in practice at present it leaves comments open two levels +" deep. But as long as you stay away from that particular case, I *believe* +" the highlighting is correct. Due to the way Vim's syntax engine works +" (greedy for start matches, unlike Rust's tokeniser which is searching for +" the earliest-starting match, start or end), I believe this cannot be solved. +" Oh you who would fix it, don't bother with things like duplicating the Block +" rules and putting ``\*\@