diff --git a/README.md b/README.md index bcd49b2..732a24e 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ helpfiles in the `doc/` directory. The helpfiles are also available via * Java (google-java-format or clang-format) * JavaScript (clang-format or [prettier](https://prettier.io)) * JSON (js-beautify) +* [OCaml](https://ocaml.org) ([ocamlformat](https://github.com/ocaml-ppx/ocamlformat)) * Proto (clang-format) * Python (Autopep8, Black, or YAPF) * Rust ([rustfmt](https://github.com/rust-lang/rustfmt)) diff --git a/autoload/codefmt.vim b/autoload/codefmt.vim index 3724f19..4637718 100644 --- a/autoload/codefmt.vim +++ b/autoload/codefmt.vim @@ -33,6 +33,7 @@ " * fish: fish_indent " * gn: gn " * go: gofmt +" * ocaml: ocamlformat " * python: autopep8, black, yapf diff --git a/autoload/codefmt/ocamlformat.vim b/autoload/codefmt/ocamlformat.vim new file mode 100644 index 0000000..2e0c429 --- /dev/null +++ b/autoload/codefmt/ocamlformat.vim @@ -0,0 +1,86 @@ +" Copyright 2020 Google Inc. All rights reserved. +" +" Licensed under the Apache License, Version 2.0 (the "License"); +" you may not use this file except in compliance with the License. +" You may obtain a copy of the License at +" +" http://www.apache.org/licenses/LICENSE-2.0 +" +" Unless required by applicable law or agreed to in writing, software +" distributed under the License is distributed on an "AS IS" BASIS, +" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +" See the License for the specific language governing permissions and +" limitations under the License. + + +let s:plugin = maktaba#plugin#Get('codefmt') + + +"" +" @private +" Formatter: ocamlformat +function! codefmt#ocamlformat#GetFormatter() abort + let l:formatter = { + \ 'name': 'ocamlformat', + \ 'setup_instructions': 'Install ocamlformat' . + \ '(https://github.com/ocaml-ppx/ocamlformat)'} + + function l:formatter.IsAvailable() abort + return executable(s:plugin.Flag('ocamlformat_executable')) + endfunction + + function l:formatter.AppliesToBuffer() abort + return &filetype is# 'ocaml' + endfunction + + "" + " Reformat the current buffer with ocamlformat or the binary named in + " @flag(ocamlformat_executable), only targeting the range between {startline} and + " {endline}. + function l:formatter.FormatRange(startline, endline) abort + let l:cmd = [ s:plugin.Flag('ocamlformat_executable'), '-' ] + let l:fname = expand('%:p') + if !empty(l:fname) + let l:cmd += ['--name', l:fname] + let l:fname_pattern = '"' . escape(l:fname, '\') . '"' + else + " assume it's an OCaml implementation file (.ml) if no file name is + " provided + let l:cmd += ['--impl'] + let l:fname_pattern = '\' + end + try + " NOTE: ocamlformat does not support range formatting. + " See https://github.com/ocaml-ppx/ocamlformat/pull/1188 + call codefmt#formatterhelpers#AttemptFakeRangeFormatting( + \ a:startline, a:endline, l:cmd) + catch /ERROR(ShellError):/ + " Parse all the errors and stick them in the quickfix list. + let l:errors = [] + let l:matchidx = 1 + while 1 + let l:tokens = matchlist(v:exception, + \ '\vFile ' . l:fname_pattern . ', line (\d+), characters (\d+)-\d+:\n(.*)\n', 0, l:matchidx) + if empty(l:tokens) + break + endif + call add(l:errors, { + \ 'filename': @%, + \ 'lnum': l:tokens[1] + a:startline - 1, + \ 'col': l:tokens[2], + \ 'text': l:tokens[3]}) + let l:matchidx = l:matchidx + 1 + endwhile + + if empty(l:errors) + " Couldn't parse ocamlformat error format; display it all. + call maktaba#error#Shout('Error formatting file: %s', v:exception) + else + call setqflist(l:errors, 'r') + cc 1 + endif + endtry + endfunction + + return l:formatter +endfunction diff --git a/doc/codefmt.txt b/doc/codefmt.txt index c17e460..b3e278b 100644 --- a/doc/codefmt.txt +++ b/doc/codefmt.txt @@ -54,14 +54,14 @@ Default: 'dartfmt' ` The path to the js-beautify executable. Default: 'js-beautify' ` - *codefmt:black_executable* -The path to the black executable. -Default: 'black' ` - *codefmt:yapf_executable* The path to the yapf executable. Default: 'yapf' ` + *codefmt:black_executable* +The path to the black executable. +Default: 'black' ` + *codefmt:gn_executable* The path to the gn executable. Default: 'gn' ` @@ -122,6 +122,10 @@ Default: 'zprint' ` The path to the fish_indent executable. Default: 'fish_indent' ` + *codefmt:ocamlformat_executable* +The path to the ocamlformat executable. +Default: 'ocamlformat' ` + *codefmt:plugin[autocmds]* Configures whether plugin/autocmds.vim should be loaded. Default: 1 ` @@ -168,6 +172,7 @@ The current list of defaults by filetype is: * fish: fish_indent * gn: gn * go: gofmt + * ocaml: ocamlformat * python: autopep8, black, yapf ============================================================================== diff --git a/instant/flags.vim b/instant/flags.vim index b501a16..74d8fe1 100644 --- a/instant/flags.vim +++ b/instant/flags.vim @@ -168,3 +168,7 @@ call s:plugin.Flag('zprint_executable', 'zprint') "" " The path to the fish_indent executable. call s:plugin.Flag('fish_indent_executable', 'fish_indent') + +"" +" The path to the ocamlformat executable. +call s:plugin.Flag('ocamlformat_executable', 'ocamlformat') diff --git a/plugin/register.vim b/plugin/register.vim index 1770f4c..7a20aab 100644 --- a/plugin/register.vim +++ b/plugin/register.vim @@ -38,3 +38,4 @@ call s:registry.AddExtension(codefmt#googlejava#GetFormatter()) call s:registry.AddExtension(codefmt#shfmt#GetFormatter()) call s:registry.AddExtension(codefmt#zprint#GetFormatter()) call s:registry.AddExtension(codefmt#fish_indent#GetFormatter()) +call s:registry.AddExtension(codefmt#ocamlformat#GetFormatter()) diff --git a/vroom/ocamlformat.vroom b/vroom/ocamlformat.vroom new file mode 100644 index 0000000..f25e3b3 --- /dev/null +++ b/vroom/ocamlformat.vroom @@ -0,0 +1,100 @@ +The built-in ocamlformat formatter knows how to format ocaml code. +If you aren't familiar with basic codefmt usage yet, see main.vroom first. + +We'll set up codefmt and configure the vroom environment, then jump into some +examples. + + :source $VROOMDIR/setupvroom.vim + + :let g:repeat_calls = [] + :function FakeRepeat(...) + | call add(g:repeat_calls, a:000) + :endfunction + :call maktaba#test#Override('repeat#set', 'FakeRepeat') + + :call codefmt#SetWhetherToPerformIsAvailableChecksForTesting(0) + + +The ocamlformat formatter expects the ocamlformat executable to be installed on your system. + + @clear + % f() + :FormatCode ocamlformat + ! ocamlformat .* + $ ;; + $ f () + ;; + f () + @end + +The name or path of the ocamlformat executable can be configured via the +ocamlformat_executable flag if the default of "ocamlformat" doesn't work. + + @clear + :Glaive codefmt ocamlformat_executable='myocamlformat' + :FormatCode ocamlformat + ! myocamlformat .* + $ ;; + $ f () + :Glaive codefmt ocamlformat_executable='ocamlformat' + + +You can format any buffer with ocamlformat specifying the formatter explicitly. + + @clear + % let x=5 in + | let inc x=x+1 in + | print_endline (string_of_int (inc x)) + + :FormatCode ocamlformat + ! ocamlformat .* + $ ;; + $ let x = 5 in + $ let inc x = x + 1 in + $ print_endline (string_of_int (inc x)) + ;; + let x = 5 in + let inc x = x + 1 in + print_endline (string_of_int (inc x)) + @end + +The ocaml filetype will use the ocamlformat formatter by default. The path to the +file being edited is passed to ocamlformat so that it can adjust to whether the +file is an implementation or an interface file. + + @clear + % f() + + :silent file /foo/bar/test.ml + :set filetype=ocaml + :FormatCode + ! ocamlformat - --name /foo/bar/test.ml .* + $ ;; + $ f () + ;; + f () + @end + +It can format specific line ranges of code using :FormatLines. + + @clear + % print_string "Hello";; + |let x=1 in let y=2 in + |y+2 + + :2,3FormatLines ocamlformat + ! ocamlformat .* + $ ;; + $ let x = 1 in + $ let y = 2 in + $ y + 2 + print_string "Hello";; + ;; + let x = 1 in + let y = 2 in + y + 2 + @end + +NOTE: the ocamlformat formatter does not natively support range formatting, so there +are certain limitations like not being able to format misaligned braces. +