diff --git a/README.md b/README.md index 6e9a9ed..5a6d060 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,53 @@ [![Travis Build Status](https://travis-ci.org/google/vim-codefmt.svg?branch=master)](https://travis-ci.org/google/vim-codefmt) -codefmt is a utility for syntax-aware code formatting. It contains several +codefmt is a utility for syntax-aware code formatting. It contains several built-in formatters, and allows new formatters to be registered by other plugins. For details, see the executable documentation in the `vroom/` directory or the -helpfiles in the `doc/` directory. The helpfiles are also available via -`:help codefmt` if codefmt is installed (and helptags have been generated). +helpfiles in the `doc/` directory. The helpfiles are also available via `:help +codefmt` if codefmt is installed (and helptags have been generated). # Supported File-types -* [Bazel](https://www.github.com/bazelbuild/bazel) BUILD files (buildifier) -* C, C++ (clang-format) -* [Clojure](https://clojure.org/) ([zprint](https://github.com/kkinnear/zprint), - [cljstyle](https://github.com/greglook/cljstyle)) -* CSS, Sass, SCSS, Less (js-beautify) -* Dart (dartfmt) -* Fish ([fish_indent](https://fishshell.com/docs/current/commands.html#fish_indent)) -* Go (gofmt) -* [GN](https://www.chromium.org/developers/gn-build-configuration) (gn) -* Haskell ([ormolu](https://github.com/tweag/ormolu)) -* HTML (js-beautify) -* Java (google-java-format or clang-format) -* JavaScript (clang-format or [prettier](https://prettier.io)) -* JSON (js-beautify) -* Kotlin ([ktfmt](https://github.com/facebookincubator/ktfmt)) -* OCaml ([ocamlformat](https://github.com/ocaml-ppx/ocamlformat)) -* Proto (clang-format) -* Python (Autopep8, Black, or YAPF) -* Ruby ([rubocop](https://rubocop.org)) -* Rust ([rustfmt](https://github.com/rust-lang/rustfmt)) -* TypeScript (clang-format) -* Shell (shfmt) -* [Vue](http://vuejs.org) (prettier) -* Nix (nixpkgs-fmt) -* Swift ([swift-format](https://github.com/apple/swift-format)) +* [Bazel](https://www.github.com/bazelbuild/bazel) BUILD files (buildifier) +* C, C++ (clang-format) +* [Clojure](https://clojure.org/) + ([zprint](https://github.com/kkinnear/zprint), + [cljstyle](https://github.com/greglook/cljstyle)) +* CSS, Sass, SCSS, Less (js-beautify, prettier) +* Dart (dartfmt) +* Elixir ([`mix format`](https://hexdocs.pm/mix/main/Mix.Tasks.Format.html)) +* Fish + ([fish_indent](https://fishshell.com/docs/current/commands.html#fish_indent)) +* [GN](https://www.chromium.org/developers/gn-build-configuration) (gn) +* Go (gofmt) +* Haskell ([ormolu](https://github.com/tweag/ormolu)) +* HTML (js-beautify, prettier) +* Java (google-java-format or clang-format) +* JavaScript (clang-format, js-beautify, or [prettier](https://prettier.io)) +* JSON (js-beautify) +* Jsonnet ([jsonnetfmt](https://jsonnet.org/learning/tools.html)) +* Julia ([JuliaFormatter](https://github.com/domluna/JuliaFormatter.jl)) +* Kotlin ([ktfmt](https://github.com/facebookincubator/ktfmt)) +* Lua + ([FormatterFiveOne](https://luarocks.org/modules/ElPiloto/formatterfiveone)) +* Markdown (prettier) +* Nix (nixpkgs-fmt) +* OCaml ([ocamlformat](https://github.com/ocaml-ppx/ocamlformat)) +* Protocol Buffers (clang-format) +* Python (Autopep8, Black, isort, Ruff, or YAPF) +* Ruby ([rubocop](https://rubocop.org)) +* Rust ([rustfmt](https://github.com/rust-lang/rustfmt)) +* Shell (shfmt) +* Swift ([swift-format](https://github.com/apple/swift-format)) +* TypeScript (clang-format) +* [Vue](http://vuejs.org) (prettier) # Commands -Use `:FormatLines` to format a range of lines or use `:FormatCode` to format -the entire buffer. Use `:NoAutoFormatBuffer` to disable current buffer -formatting. +Use `:FormatLines` to format a range of lines or use `:FormatCode` to format the +entire buffer. Use `:NoAutoFormatBuffer` to disable current buffer formatting. # Usage example @@ -78,8 +85,8 @@ Glaive codefmt plugin[mappings] Glaive codefmt google_java_executable="java -jar /path/to/google-java-format-VERSION-all-deps.jar" ``` -Make sure you have updated maktaba recently. Codefmt depends upon maktaba -to register formatters. +Make sure you have updated maktaba recently. Codefmt depends upon maktaba to +register formatters. # Autoformatting @@ -89,27 +96,38 @@ Want to just sit back and let autoformat happen automatically? Add this to your ```vim augroup autoformat_settings autocmd FileType bzl AutoFormatBuffer buildifier - autocmd FileType c,cpp,proto,javascript,arduino AutoFormatBuffer clang-format + autocmd FileType c,cpp,proto,javascript,typescript,arduino AutoFormatBuffer clang-format + autocmd FileType clojure AutoFormatBuffer cljstyle autocmd FileType dart AutoFormatBuffer dartfmt - autocmd FileType go AutoFormatBuffer gofmt + autocmd FileType elixir,eelixir,heex AutoFormatBuffer mixformat + autocmd FileType fish AutoFormatBuffer fish_indent autocmd FileType gn AutoFormatBuffer gn + autocmd FileType go AutoFormatBuffer gofmt + autocmd FileType haskell AutoFormatBuffer ormolu + " Alternative for web languages: prettier autocmd FileType html,css,sass,scss,less,json AutoFormatBuffer js-beautify autocmd FileType java AutoFormatBuffer google-java-format + autocmd FileType jsonnet AutoFormatBuffer jsonnetfmt + autocmd FileType julia AutoFormatBuffer JuliaFormatter + autocmd FileType kotlin AutoFormatBuffer ktfmt + autocmd FileType lua AutoFormatBuffer luaformatterfiveone + autocmd FileType markdown AutoFormatBuffer prettier + autocmd FileType ocaml AutoFormatBuffer ocamlformat autocmd FileType python AutoFormatBuffer yapf " Alternative: autocmd FileType python AutoFormatBuffer autopep8 + autocmd FileType ruby AutoFormatBuffer rubocop autocmd FileType rust AutoFormatBuffer rustfmt - autocmd FileType vue AutoFormatBuffer prettier autocmd FileType swift AutoFormatBuffer swift-format + autocmd FileType vue AutoFormatBuffer prettier augroup END ``` # Configuring formatters Most formatters have some options available that can be configured via -[Glaive](https://www.github.com/google/vim-glaive) -You can get a quick view of all codefmt flags by executing `:Glaive codefmt`, or -start typing flag names and use tab completion. See `:help Glaive` for usage -details. +[Glaive](https://www.github.com/google/vim-glaive) You can get a quick view of +all codefmt flags by executing `:Glaive codefmt`, or start typing flag names and +use tab completion. See `:help Glaive` for usage details. # Installing formatters @@ -127,25 +145,22 @@ vroom/FORMATTER-NAME.vroom to learn more about usage for individual formatters. ## Creating a New Formatter Assume a filetype `myft` and a formatter called `MyFormatter`. Our detailed -guide to creating a formatter [lives -here](https://github.com/google/vim-codefmt/wiki/Formatter-Integration-Guide). +guide to creating a formatter +[lives here](https://github.com/google/vim-codefmt/wiki/Formatter-Integration-Guide). * Create an issue for your new formatter and discuss! * Create a new file in `autoload/codefmt/myformatter.vim` See - `autoload/codefmt/buildifier.vim for an example. This is where all the - logic for formatting goes. + `autoload/codefmt/buildifier.vim for an example. This is where all the logic + for formatting goes. -* Register the formatter in - [plugin/register.vim](plugin/register.vim) - with: +* Register the formatter in [plugin/register.vim](plugin/register.vim) with: ```vim call s:registry.AddExtension(codefmt#myformatter#GetFormatter()) ``` -* Create a flag in - [instant/flags.vim](instant/flags.vim) +* Create a flag in [instant/flags.vim](instant/flags.vim) ```vim "" diff --git a/autoload/codefmt/juliaformatter.vim b/autoload/codefmt/juliaformatter.vim new file mode 100644 index 0000000..9a3f898 --- /dev/null +++ b/autoload/codefmt/juliaformatter.vim @@ -0,0 +1,76 @@ +" Copyright 2023 Google LLC +" +" 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') + +let s:checkedInstall = 0 + +"" +" @private +" Formatter: JuliaFormatter +function! codefmt#juliaformatter#GetFormatter() abort + let l:installer = + \ maktaba#path#Join([s:plugin.location, 'bin', 'julia', 'install']) + let l:formatter = { + \ 'name': 'JuliaFormatter', 'setup_instructions': 'Run ' . l:installer} + + function l:formatter.IsAvailable() abort + let l:cmd = codefmt#formatterhelpers#ResolveFlagToArray('julia_format_executable') + if codefmt#ShouldPerformIsAvailableChecks() + if !executable(l:cmd[0]) + return 0 + endif + if !s:checkedInstall + let s:checkedInstall = 1 + let l:syscall = maktaba#syscall#Create([cmd[0], "--check-install"]) + call l:syscall.Call(0) + if v:shell_error != 0 + return 0 + endif + endif + endif + return 1 + endfunction + + function l:formatter.AppliesToBuffer() abort + return codefmt#formatterhelpers#FiletypeMatches(&filetype, 'julia') + endfunction + + "" + " Reformat the current buffer using formatjulia.jl, only targeting {ranges}. + function l:formatter.FormatRanges(ranges) abort + if empty(a:ranges) + return + endif + for [l:startline, l:endline] in a:ranges + call maktaba#ensure#IsNumber(l:startline) + call maktaba#ensure#IsNumber(l:endline) + endfor + let l:exec = s:plugin.Flag('julia_format_executable') + if empty(l:exec) + let l:cmd = [maktaba#path#Join( + \ [s:plugin.location, 'bin', 'julia', 'formatjulia.jl'])] + else + " Split the command on spaces, unless preceeded by a backslash + let l:cmd = split(l:exec, '\\\@ x[0] . ':' . x[1]}) + call codefmt#formatterhelpers#Format(l:cmd) + endfunction + + return l:formatter +endfunction diff --git a/autoload/codefmt/ruff.vim b/autoload/codefmt/ruff.vim new file mode 100644 index 0000000..e2f03e9 --- /dev/null +++ b/autoload/codefmt/ruff.vim @@ -0,0 +1,71 @@ +" Copyright 2017 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') + + +function! s:FormatWithArgs(args) abort + let l:executable = s:plugin.Flag('ruff_executable') + let l:lines = getline(1, line('$')) + let l:cmd = [l:executable, 'format'] + a:args + ['-'] + if !empty(@%) + let l:cmd += ['--stdin-filename=' . @%] + endif + let l:input = join(l:lines, "\n") + let l:result = maktaba#syscall#Create(l:cmd).WithStdin(l:input).Call(0) + if v:shell_error + call maktaba#error#Shout('Error formatting file: %s', l:result.stderr) + return + endif + let l:formatted = split(l:result.stdout, "\n") + + call maktaba#buffer#Overwrite(1, line('$'), l:formatted) +endfunction + + +"" +" @private +" Formatter: ruff +function! codefmt#ruff#GetFormatter() abort + let l:formatter = { + \ 'name': 'ruff', + \ 'setup_instructions': 'Install ruff ' . + \ '(https://docs.astral.sh/ruff/).'} + + function l:formatter.IsAvailable() abort + return executable(s:plugin.Flag('ruff_executable')) + endfunction + + function l:formatter.AppliesToBuffer() abort + return codefmt#formatterhelpers#FiletypeMatches(&filetype, 'python') + endfunction + + function l:formatter.Format() abort + call s:FormatWithArgs([]) + endfunction + + "" + " Reformat the current buffer with ruff or the binary named in + " @flag(ruff_executable), only targeting the range between {startline} and + " {endline}. + " @throws ShellError + function l:formatter.FormatRange(startline, endline) abort + call maktaba#ensure#IsNumber(a:startline) + call maktaba#ensure#IsNumber(a:endline) + call s:FormatWithArgs(['--range=' . a:startline . ':' . a:endline]) + endfunction + + return l:formatter +endfunction diff --git a/bin/julia/.JuliaFormatter.toml b/bin/julia/.JuliaFormatter.toml new file mode 100644 index 0000000..fd7b4d9 --- /dev/null +++ b/bin/julia/.JuliaFormatter.toml @@ -0,0 +1,18 @@ +# Copyright 2023 Google LLC +# +# 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. + +indent = 2 +margin = 80 +trailing_comma = false +whitespace_in_kwargs = false diff --git a/bin/julia/Manifest.toml b/bin/julia/Manifest.toml new file mode 100644 index 0000000..491ceee --- /dev/null +++ b/bin/julia/Manifest.toml @@ -0,0 +1,232 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.9.3" +manifest_format = "2.0" +project_hash = "93e024a7bf6c9615a79c4b80b3a014b0f1f744c9" + +[[deps.ArgParse]] +deps = ["Logging", "TextWrap"] +git-tree-sha1 = "3102bce13da501c9104df33549f511cd25264d7d" +uuid = "c7e460c6-2fb9-53a9-8c5b-16f535851c63" +version = "1.1.4" + +[[deps.ArgTools]] +uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" +version = "1.1.1" + +[[deps.Artifacts]] +uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" + +[[deps.Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" + +[[deps.CSTParser]] +deps = ["Tokenize"] +git-tree-sha1 = "3ddd48d200eb8ddf9cb3e0189fc059fd49b97c1f" +uuid = "00ebfdb7-1f24-5e51-bd34-a7502290713f" +version = "3.3.6" + +[[deps.CommonMark]] +deps = ["Crayons", "JSON", "PrecompileTools", "URIs"] +git-tree-sha1 = "532c4185d3c9037c0237546d817858b23cf9e071" +uuid = "a80b9123-70ca-4bc0-993e-6e3bcb318db6" +version = "0.8.12" + +[[deps.Compat]] +deps = ["UUIDs"] +git-tree-sha1 = "8a62af3e248a8c4bad6b32cbbe663ae02275e32c" +uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" +version = "4.10.0" + + [deps.Compat.extensions] + CompatLinearAlgebraExt = "LinearAlgebra" + + [deps.Compat.weakdeps] + Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" + LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" + +[[deps.Crayons]] +git-tree-sha1 = "249fe38abf76d48563e2f4556bebd215aa317e15" +uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f" +version = "4.1.1" + +[[deps.DataStructures]] +deps = ["Compat", "InteractiveUtils", "OrderedCollections"] +git-tree-sha1 = "3dbd312d370723b6bb43ba9d02fc36abade4518d" +uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" +version = "0.18.15" + +[[deps.Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[deps.Downloads]] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +version = "1.6.0" + +[[deps.FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" + +[[deps.Glob]] +git-tree-sha1 = "97285bbd5230dd766e9ef6749b80fc617126d496" +uuid = "c27321d9-0574-5035-807b-f59d2c89b15c" +version = "1.3.1" + +[[deps.InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" + +[[deps.JSON]] +deps = ["Dates", "Mmap", "Parsers", "Unicode"] +git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "0.21.4" + +[[deps.JuliaFormatter]] +deps = ["CSTParser", "CommonMark", "DataStructures", "Glob", "Pkg", "PrecompileTools", "Tokenize"] +git-tree-sha1 = "3d5b5b539e4606dcca0e6a467b98a64c8da4850b" +uuid = "98e50ef6-434e-11e9-1051-2b60c6c9e899" +version = "1.0.42" + +[[deps.LibCURL]] +deps = ["LibCURL_jll", "MozillaCACerts_jll"] +uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" +version = "0.6.3" + +[[deps.LibCURL_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] +uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" +version = "7.84.0+0" + +[[deps.LibGit2]] +deps = ["Base64", "NetworkOptions", "Printf", "SHA"] +uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" + +[[deps.LibSSH2_jll]] +deps = ["Artifacts", "Libdl", "MbedTLS_jll"] +uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" +version = "1.10.2+0" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" + +[[deps.Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" + +[[deps.Markdown]] +deps = ["Base64"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" + +[[deps.MbedTLS_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" +version = "2.28.2+0" + +[[deps.Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" + +[[deps.MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2022.10.11" + +[[deps.NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +version = "1.2.0" + +[[deps.OrderedCollections]] +git-tree-sha1 = "2e73fe17cac3c62ad1aebe70d44c963c3cfdc3e3" +uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +version = "1.6.2" + +[[deps.Parsers]] +deps = ["Dates", "PrecompileTools", "UUIDs"] +git-tree-sha1 = "716e24b21538abc91f6205fd1d8363f39b442851" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "2.7.2" + +[[deps.Pkg]] +deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] +uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +version = "1.9.2" + +[[deps.PrecompileTools]] +deps = ["Preferences"] +git-tree-sha1 = "03b4c25b43cb84cee5c90aa9b5ea0a78fd848d2f" +uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +version = "1.2.0" + +[[deps.Preferences]] +deps = ["TOML"] +git-tree-sha1 = "00805cd429dcb4870060ff49ef443486c262e38e" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.4.1" + +[[deps.Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[deps.REPL]] +deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] +uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" + +[[deps.Random]] +deps = ["SHA", "Serialization"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" + +[[deps.Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" + +[[deps.Sockets]] +uuid = "6462fe0b-24de-5631-8697-dd941f90decc" + +[[deps.TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.3" + +[[deps.Tar]] +deps = ["ArgTools", "SHA"] +uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +version = "1.10.0" + +[[deps.TextWrap]] +git-tree-sha1 = "9250ef9b01b66667380cf3275b3f7488d0e25faf" +uuid = "b718987f-49a8-5099-9789-dcd902bef87d" +version = "1.0.1" + +[[deps.Tokenize]] +git-tree-sha1 = "0454d9a9bad2400c7ccad19ca832a2ef5a8bc3a1" +uuid = "0796e94c-ce3b-5d07-9a54-7f471281c624" +version = "0.5.26" + +[[deps.URIs]] +git-tree-sha1 = "67db6cc7b3821e19ebe75791a9dd19c9b1188f2b" +uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4" +version = "1.5.1" + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" + +[[deps.Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" + +[[deps.Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.2.13+0" + +[[deps.nghttp2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" +version = "1.48.0+0" + +[[deps.p7zip_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +version = "17.4.0+0" diff --git a/bin/julia/Project.toml b/bin/julia/Project.toml new file mode 100644 index 0000000..54c0080 --- /dev/null +++ b/bin/julia/Project.toml @@ -0,0 +1,8 @@ +name = "julia" +uuid = "f7e72769-0d4f-4e1c-bb39-275b23f83607" +authors = ["Trevor Stone "] +version = "0.1.0" + +[deps] +ArgParse = "c7e460c6-2fb9-53a9-8c5b-16f535851c63" +JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899" diff --git a/bin/julia/format.jl b/bin/julia/format.jl new file mode 100755 index 0000000..a363912 --- /dev/null +++ b/bin/julia/format.jl @@ -0,0 +1,230 @@ +#!/usr/bin/env julia +# Copyright 2023 Google LLC +# +# 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. + +# This program wraps the JuliaFormatter package with a command line interface +# that takes ranges of lines. Example: +# format.jl --file-path path/to/mycode.jl --lines 1:7 20:35 < mycode.jl +# The bin/format.jl script that ships with JuliaFormatter doesn't take input +# on stdin and doesn't support line ranges, both of which are nice features for +# the vim-codefmt plugin. The --file-path flag lets this program find +# .JuliaFormatter.toml files to determine code style preferences. + +try + @eval using JuliaFormatter +catch ArgumentError + println( + stderr, + "Missing JuliaFormatter package, run $(dirname(PROGRAM_FILE))/install" + ) + exit(2) +end +try + @eval using ArgParse +catch ArgumentError + println( + stderr, + "Missing ArgParse package, run $(dirname(PROGRAM_FILE))/install" + ) + exit(2) +end + +"A range of line numbers to format. Requires `0 < first <= last`." +struct LineRange + first::Int + last::Int + LineRange(first, last) = + first <= 0 || last < first ? error("Invalid line range $first:$last") : + new(first, last) +end + +Base.string(r::LineRange) = "$(r.first):$(r.last)" + +function ArgParse.parse_item(::Type{LineRange}, s::AbstractString) + parts = split(s, ':') + length(parts) == 2 || + throw(ArgumentError("LineRange expecting start:end, got $s")) + LineRange(parse(Int, parts[1]), parse(Int, parts[2])) +end + +"Entry point to run format.jl. argv is the command line arguments." +function main(argv::Vector{<:AbstractString}) + s = ArgParseSettings( + "$(basename(PROGRAM_FILE)): format all or part of Julia code read from stdin", + autofix_names=true + ) + @add_arg_table! s begin + #! format: off + "--file_path" + help = "file path of the code (default: current working directory)" + metavar = "path/to/file.jl" + "--lines" + help = "line range(s) to format (1-based)" + arg_type = LineRange + metavar = "first:last" + nargs = '*' + "--check_install" + help = "exit with status 0 if dependencies are installed, 2 otherwise" + action = :store_true + #! format: on + end + args = parse_args(argv, s, as_symbols=true) + if args[:check_install] + exit(0) # if we got this far, module import succeeded + end + file_path = let p = args[:file_path] + fakefile = "file-path-not-specified" + isnothing(p) ? joinpath(pwd(), fakefile) : abspath(expanduser(p)) + end + # Sort line ranges and check for overlap, which would make things complicated + ranges = sort(args[:lines], by=x -> x.first) + for i = 2:length(ranges) + if ranges[i].first <= ranges[i-1].last + println( + stderr, + "Overlapping --lines ranges $(ranges[i-1]) and $(ranges[i])" + ) + exit(3) + end + end + + config = JuliaFormatter.Configuration( + Dict{String,Any}(JuliaFormatter.find_config_file(file_path)) + ) + opts = [Symbol(k) => v for (k, v) in pairs(config)] + try + if isempty(ranges) + input = read(stdin, String) + output = JuliaFormatter.format_text(input; opts...) + print(output) + else + formatranges(ranges, opts) + end + catch e + message = isdefined(e, :msg) ? e.msg : string(e) + println(stderr, "Format error: $message") + exit(1) + end +end + +"""Formats one or more line ranges of `stdin` using options `opts`. +Assumes `ranges` is already sorted. Prints formatted result to `stdout`. +""" +function formatranges(ranges::Vector{LineRange}, opts) + # JuliaFormatter doesn't support line ranges, so use format comment directives + # to turn it on and off at appropriate times. Use a random number as a marker + # so added directives can be removed after. + # NOTE: This approach means line numbers for syntax errors are misleading. + marker = string(rand(UInt32)) + formaton = "# added:$marker\n#! format: on\n" + formatoff = "# added:$marker\n#! format: off\n" + formatpat = r"\s*#! format: (on|off)\s*$" + lines = readlines(stdin) + lnum = 1 # current index in lines; unaffected by directive additions + text = IOBuffer() # will contain the input file with format directives added + requested = true # whether formatting would be on at this point if the formatting were done without line ranges + # disable formatting unless line 1 is in range + if ranges[1].first > 1 + print(text, formatoff) + end + for (ri, range) in enumerate(ranges) + # for each line range, add all the lines leading up to the range to text, + # disabling any format directives so we don't turn formatting on outside of + # the requested ranges + for i = lnum:range.first-1 + if lnum > length(lines) + @goto eof + end + line = lines[lnum] + lnum += 1 + # disable existing formatter directives + if (m = match(formatpat, line)) !== nothing + line = "# disabled:$marker:$line" + requested = m.captures[1] == "on" + end + println(text, line) + end + # if directives wouldn't have disabled this range, turn on formatting + if requested + print(text, formaton) + end + # add each line in the range to text + for i = range.first:range.last + if lnum > length(lines) + @goto eof + end + line = lines[lnum] + lnum += 1 + # if there's a format:off directive inside the range, respect that; + # if there's a format:on directive inside the range and formatting had + # been off, enable it at this point + if (m = match(formatpat, line)) !== nothing + line = "# disabled:$marker:$line" + if m.captures[1] == "on" && !requested + requested = true + print(text, formaton) + elseif m.captures[1] == "off" && requested + requested = false + print(text, formatoff) + end + end + println(text, line) + end + # turn off formatting at the end of the range + if lnum <= length(lines) + print(text, formatoff) + end + end + # process lines after the last range + while lnum <= length(lines) + line = lines[lnum] + lnum += 1 + if occursin(formatpat, line) + line = "# disabled:$marker:$line" + end + println(text, line) + end + @label eof + # work around https://github.com/domluna/JuliaFormatter.jl/issues/777 + # by appending on and off directives at the end + print(text, formaton) + print(text, formatoff) + # now that format directives have been added, format the whole thing + input = String(take!(text)) + output = JuliaFormatter.format_text(input; opts...) + # remove format directives we added and restore ones we disabled + skipnext = false + addedpat = Regex("^\\s*#\\s*added:$marker\\s*\$") + disabledpat = Regex("^\\s*#\\s*disabled:$marker:(.*)", "s") + all = [] + last = "nothing" + for (i, line) in enumerate(readlines(IOBuffer(output))) + push!(all, line) + last = line + if skipnext + skipnext = false + elseif occursin(addedpat, line) + skipnext = true + else + if (m = match(disabledpat, line)) !== nothing + line = m.captures[1] + end + println(line) + end + end +end + +if abspath(PROGRAM_FILE) == @__FILE__ + main(ARGS) +end diff --git a/bin/julia/install b/bin/julia/install new file mode 100755 index 0000000..c1e54be --- /dev/null +++ b/bin/julia/install @@ -0,0 +1,4 @@ +#!/bin/sh + +PROJECT=$(dirname $0) +julia --project="$PROJECT" -e 'using Pkg; Pkg.instantiate(verbose=true)' diff --git a/bin/julia/install.cmd b/bin/julia/install.cmd new file mode 100644 index 0000000..bc7b8ad --- /dev/null +++ b/bin/julia/install.cmd @@ -0,0 +1,3 @@ +@echo off +set PROJECT=%~dp0 +julia --project="%PROJECT%" -e 'using Pkg; Pkg.instantiate(verbose=true)' diff --git a/doc/codefmt.txt b/doc/codefmt.txt index b360888..90482bd 100644 --- a/doc/codefmt.txt +++ b/doc/codefmt.txt @@ -45,11 +45,12 @@ The current list of defaults by filetype is: * javascript, json, html, css: js-beautify * javascript, html, css, markdown: prettier * json, jsonnet: jsonnetfmt + * julia: JuliaFormatter * kotlin: ktfmt * lua: luaformatterfiveone * nix: nixpkgs-fmt * ocaml: ocamlformat - * python: autopep8, black, yapf + * python: autopep8, black, ruff, yapf * ruby: rubocop * rust: rustfmt * sh: shfmt @@ -97,6 +98,10 @@ The path to the mix executable for Elixir. String, list, or callable that takes no args and returns a string or a list with command line arguments. Default: 'mix' ` + *codefmt:ruff_executable* +The path to the ruff executable. +Default: 'ruff' ` + *codefmt:yapf_executable* The path to the yapf executable. Default: 'yapf' ` @@ -149,6 +154,10 @@ Default: '' ` The path to the jsonnetfmt executable. Default: 'jsonnetfmt' ` + *codefmt:julia_format_executable* +The path to the Julia formatter script, uses `bin/formatjulia.jl` bundled with +this plugin. + *codefmt:google_java_executable* The path to the google-java executable. Generally, this should have the form: `java -jar /path/to/google-java` diff --git a/instant/flags.vim b/instant/flags.vim index d43c179..ab636a9 100644 --- a/instant/flags.vim +++ b/instant/flags.vim @@ -87,6 +87,10 @@ call s:plugin.Flag('js_beautify_executable', 'js-beautify') " takes no args and returns a string or a list with command line arguments. call s:plugin.Flag('mix_executable', 'mix') +"" +" The path to the ruff executable. +call s:plugin.Flag('ruff_executable', 'ruff') + "" " The path to the yapf executable. call s:plugin.Flag('yapf_executable', 'yapf') @@ -138,6 +142,12 @@ call s:plugin.Flag('buildifier_warnings', '') " The path to the jsonnetfmt executable. call s:plugin.Flag('jsonnetfmt_executable', 'jsonnetfmt') +"" +" The path to the Julia formatter script, uses `bin/formatjulia.jl` bundled with +" this plugin. +call s:plugin.Flag('julia_format_executable', + \ maktaba#path#Join([expand(':h:h'), 'bin', 'julia', 'format.jl'])) + "" " The path to the google-java executable. Generally, this should have the " form: diff --git a/plugin/register.vim b/plugin/register.vim index 36f9d6c..1964111 100644 --- a/plugin/register.vim +++ b/plugin/register.vim @@ -39,11 +39,12 @@ " * javascript, json, html, css: js-beautify " * javascript, html, css, markdown: prettier " * json, jsonnet: jsonnetfmt +" * julia: JuliaFormatter " * kotlin: ktfmt " * lua: luaformatterfiveone " * nix: nixpkgs-fmt " * ocaml: ocamlformat -" * python: autopep8, black, yapf +" * python: autopep8, black, ruff, yapf " * ruby: rubocop " * rust: rustfmt " * sh: shfmt @@ -74,12 +75,14 @@ call s:registry.AddExtension(codefmt#googlejava#GetFormatter()) call s:registry.AddExtension(codefmt#jsonnetfmt#GetFormatter()) call s:registry.AddExtension(codefmt#jsbeautify#GetFormatter()) call s:registry.AddExtension(codefmt#prettier#GetFormatter()) +call s:registry.AddExtension(codefmt#juliaformatter#GetFormatter()) call s:registry.AddExtension(codefmt#ktfmt#GetFormatter()) call s:registry.AddExtension(codefmt#luaformatterfiveone#GetFormatter()) call s:registry.AddExtension(codefmt#nixpkgs_fmt#GetFormatter()) call s:registry.AddExtension(codefmt#autopep8#GetFormatter()) call s:registry.AddExtension(codefmt#isort#GetFormatter()) call s:registry.AddExtension(codefmt#black#GetFormatter()) +call s:registry.AddExtension(codefmt#ruff#GetFormatter()) call s:registry.AddExtension(codefmt#yapf#GetFormatter()) call s:registry.AddExtension(codefmt#rubocop#GetFormatter()) call s:registry.AddExtension(codefmt#rustfmt#GetFormatter()) diff --git a/vroom/juliaformatter.vroom b/vroom/juliaformatter.vroom new file mode 100644 index 0000000..804cc3a --- /dev/null +++ b/vroom/juliaformatter.vroom @@ -0,0 +1,78 @@ +The built-in JuliaFormatter formatter knows how to format Julia files. 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 JuliaFormatter formatter uses the bin/julia/format.jl script which is +bundled with codefmt. That script will return an error if Julia or the +JuliaFormatter package are not installed. + + % module Foo bar(x) = x ? "yes" : "no" end + :FormatCode JuliaFormatter + ! .*/bin/julia/format.jl .* + $ module Foo { + $ function bar(x) + $ if x + $ "yes" + $ else + $ "no" + $ end + $ end + $ end + +The name or path of the format.jl script can be configured via the +julia_format_executable flag if the bundled format.jl doesn't work. + + :Glaive codefmt julia_format_executable='/path/to/myscript' + :FormatCode JuliaFormatter + ! /path/to/myscript .* + $ module Foo + $ function bar(x) + $ if x + $ "yes" + $ else + $ "no" + $ end + $ end + $ end + :let g:format_jl = maktaba#path#Join([expand("$VROOMDIR:h:h"), 'bin', 'julia', 'format.jl']) + :Glaive codefmt julia_format_executable=`g:format_jl` + +It can format specific line ranges of code using :FormatLines. + + @clear + % module Foo + |function bar(x) + |print(x ? "yes" : "no") + |print( + |x && + |!x ? + |"impossible" : + |"ok") + |end + |end + + :4,8FormatLines JuliaFormatter + ! .*/bin/julia/format.jl .*--lines 4:8.* + $ module Foo { + $ function bar(x) + $ print(x ? "yes" : "no") + $ print(if x && !x + $ "impossible" + $ else + $ "ok" + $ end) + $ end + $ end diff --git a/vroom/ruff.vroom b/vroom/ruff.vroom new file mode 100644 index 0000000..e2fc21d --- /dev/null +++ b/vroom/ruff.vroom @@ -0,0 +1,71 @@ +The built-in ruff formatter knows how to format python 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 ruff formatter expects the ruff executable to be installed on your +system. + + :silent file somefile.py + % f() + :FormatCode ruff + ! ruff format - --stdin-filename=somefile.py.* + $ f() + +The name or path of the ruff executable can be configured via the +ruff_executable flag if the default of "ruff" doesn't work. + + :Glaive codefmt ruff_executable='/somepath/ruff' + :FormatCode ruff + ! /somepath/ruff format -.* + $ f() + :Glaive codefmt ruff_executable='ruff' + + +You can format any buffer with ruff specifying the formatter explicitly. + + @clear + % if True: pass + + :FormatCode ruff + ! ruff format -.* + $ if True: + $ pass + if True: + pass + @end + +It can format specific line ranges of code using :FormatLines. + + @clear + % some_tuple=( 1,2, 3,'a' ); + |if bar : bar+=1; bar=bar* bar + |else: bar-=1; + + :2,3FormatLines ruff + ! ruff format .*--range=2:3 -.* + $ some_tuple=( 1,2, 3,'a' ); + $ if bar: + $ bar += 1 + $ bar = bar * bar + $ else: + $ bar -= 1 + some_tuple=( 1,2, 3,'a' ); + if bar: + bar += 1 + bar = bar * bar + else: + bar -= 1 + @end