A code formatter with rules set in stone.
Runic is a formatter for the Julia programming language built on top of JuliaSyntax.jl.
Similarly to gofmt, Runic have no configuration. The
formatting rules are set in stone (although not yet complete). This approach is something
that is appreciated by most Go programmers, see for example the following
quote:
Gofmt's style is no one's favorite, yet gofmt is everyone's favorite.
In Julia v1.12 and later, Runic can be installed as a Pkg app:
julia -e 'using Pkg; Pkg.Apps.add("Runic")'
Assuming ~/.julia/bin is in your PATH you can now invoke runic from your shell, e.g.:
runic --version # Show version info
runic --help # Show documentation
# Format all files in-place in the current directory (recursively)
runic --inplace .
[!NOTE] When walking directories Runic refuses to recurse into your home directory and into Julia depot subdirectories (e.g.
~/.julia/packages), and skips nested git repositories (e.g. git submodules). Pass--recurseto include nested git repositories and--force-recurseto disable all checks.
The following copy-pasteable snippet installs Runic (in a @runic shared environment) and
the runic and git-runic wrappers (in .local/bin).
# Install Runic
julia --project=@runic --startup-file=no -e 'using Pkg; Pkg.add("Runic")'
# Install the runic shell script
curl -fsSL -o ~/.local/bin/runic https://raw.githubusercontent.com/fredrikekre/Runic.jl/refs/heads/master/bin/runic
chmod +x ~/.local/bin/runic
# Install the git-runic shell script
curl -fsSL -o ~/.local/bin/git-runic https://raw.githubusercontent.com/fredrikekre/Runic.jl/refs/heads/master/bin/git-runic
chmod +x ~/.local/bin/git-runic
The CLI is the main interface to Runic. runic --help will show all available options
(output included below). Some example invocations are listed here.
Format a single file in place:
runic --inplace file.jl
Format all files in a directory (recursively) in place:
runic --inplace src/
Verify formatting of all files in a directory with verbose and diff output:
runic --check --diff --verbose src/
Format the content of standard in and print the result to standard out:
echo "1+1" | runic
Format Julia code blocks inside docstrings of a Julia source file
(fenced ```julia, ```julia-repl, and ```jldoctest
blocks, plus indented-code blocks — which covers leading method signatures):
runic --docstrings --inplace src/
Format Julia code blocks inside Markdown files. Dispatch is by file extension — any
file whose name ends in .md or .qmd (Quarto) is routed through the Markdown
formatter, which only rewrites the embedded Julia code blocks and leaves the prose
alone. In addition to the fences listed above, Quarto executable code cells
(```{julia}) are recognized as Julia code blocks:
runic --inplace README.md # explicit path — extension handles it
runic --extensions=jl,md,qmd --inplace . # directory walk picks up all three kinds
echo '```julia' > foo.md && echo '1+1' >> foo.md && echo '```' >> foo.md
cat foo.md | runic --stdin-filename=foo.md # stdin dispatch via virtual filename
Output of runic --help for a complete list of options:
$ runic --help
NAME
Runic.main - format Julia source code
SYNOPSIS
julia -m Runic [<options>] <path>...
DESCRIPTION
`Runic.main` (typically invoked as `julia -m Runic`) formats Julia source
code using the Runic.jl formatter.
OPTIONS
<path>...
Input path(s) (files and/or directories) to process. For directories,
all files matching `--extensions` are collected recursively.
If no path is given, or if path is `-`, input is read from stdin.
-c, --check
Do not write output and exit with a non-zero code if the input is not
formatted correctly.
-d, --diff
Print the diff between the input and formatted output to stderr.
Requires `git` to be installed.
--docstrings
Format code blocks in docstrings embedded in source files.
--extensions=<ext>[,<ext>...]
Comma-separated list of file extensions to collect when walking
directories. Defaults to `jl`. Use e.g. `--extensions=jl,md` to
pick up both Julia and Markdown files. Explicit file paths bypass this
filter.
--force-recurse
Disable the safety checks that prevent formatting of the home
directory and Julia depot subdirectories (e.g. `~/.julia/packages`).
Implies `--recurse`.
--help
Print this message.
-i, --inplace
Format files in place.
--lines=<start line>:<end line>
Limit formatting to the line range <start line> to <end line>. Multiple
ranges can be formatted by specifying multiple --lines arguments.
-o <file>, --output=<file>
File to write formatted output to. If no output is given, or if the file
is `-`, output is written to stdout.
--recurse
Recurse into nested git repositories (e.g. git submodules and vendored
clones) when walking directories. By default nested git repositories
are skipped.
--stdin-filename=<filename>
Assumed filename when formatting from stdin. Used for error messages
and for inferring whether input is Julia or Markdown.
-v, --verbose
Enable verbose output.
--version
Print Runic and julia version information.
In addition to the CLI there is also the two function Runic.format_file and
Runic.format_string. See their respective docstrings for details.
Most code editors have code formatting capabilities and many can be configured to use Runic. Example configuration for some editors are given in the following sections.
[!NOTE] These editor configurations assume you have installed Runic as an app (as suggested in the Installation section above) and that the
runicbinary is available in the PATH environment variable that your editor is seeing. If the editor doesn't find theruniccommand you can replacerunicwith the full path to the app instead (e.g./home/sauron/.julia/bin/runic).
[!IMPORTANT] Note that these configurations depend on third party plugins. They works as advertised but use it at your own risk.
Runic can be used as a formatter in Neovim using conform.nvim. Refer to the conform.nvim repository for installation and setup instructions.
Runic is available directly in conform so the only thing that needs to be done is to configure conform to use runic for Julia files:
require("conform").setup({
formatters_by_ft = {
julia = {"runic"},
},
})
Note that conform (and thus Runic) can be used as formatexpr for the gq command. This is
enabled by adding the following to your configuration:
vim.o.formatexpr = "v:lua.require('conform').formatexpr()"
Runic can be used as a formatter in VS Code using the extension Custom Local Formatters.
After installing the extension you can configure Runic as a local formatter by adding the
following entry to your settings.json.
"customLocalFormatters.formatters": [
{
"command": "runic",
"languages": ["julia"]
}
]
Using the "Format Document" VS Code command will now format the file using Runic. Note that the first time you execute the command you will be prompted to select a formatter since the Julia language extension also comes with a formatter.
Runic can be used as a formatter in Emacs using apheleia. Refer to the apheleia repository for installation and setup instruction.
Runic is not (yet) available directly in apheleia so the
following configuration needs to be added to your .emacs.
(push `(runic . ("runic")) apheleia-formatters)
(push '(julia-mode . runic) apheleia-mode-alist)
Runic can be used as a formatter in Helix. Configure Helix's
:format command to use Runic for julia code by adding the following to the
languages.toml configuration file.
[[language]]
name = "julia"
auto-format = false
formatter = { command = "runic" }
Runic can be used as a formatter in Zed. You can configure Runic as a
local formatter by adding the following entry to your settings.json.
"languages": {
"Julia": {
"formatter": {
"external": {
"command": "runic",
}
}
}
}
The git-runic
script (a modified version of
git-clang-format)
provides a convenient way to apply Runic formatting incrementally to a code
base by limiting formatting to lines that are added or modified in each commit.
The script can be installed with the following commands (replace the two
occurrences of ~/.local/bin if needed):
# Download the script into ~/.local/bin
curl -fsSL -o ~/.local/bin/git-runic https://raw.githubusercontent.com/fredrikekre/Runic.jl/refs/heads/master/bin/git-runic
# Make the script executable
chmod +x ~/.local/bin/git-runic
# Verify the installation
git runic -h
Here is a checklist for adopting Runic formatting wholesale in a project:
runic -i <path> and commit the changes in separate
commit. This commit can be ignored in git blame (see Ignore formatting commits in git
blame).Alternatively Runic formatting can be adopted incrementally by using the
git-runic integration, see Git integration for details.
When setting up Runic formatting for a repository for the first time (or when upgrading to a
new version of Runic) the formatting commit will likely result in a large diff with mostly
non functional changes such as e.g. whitespace. Since the diff is large it is likely that it
will show up and interfere when using git-blame. To
ignore commits during git-blame you can i) add them to a file .git-blame-ignore-revs and
ii) tell git to use this file as ignore file by running
git config blame.ignoreRevsFile .git-blame-ignore-revs
See the git-blame documentation for details.
For example, such a file may look like this:
# Adding Runic formatting
<commit hash of formatting commit>
# Upgrading Runic from 1.0 to 2.0
<commit hash of formatting commit>
If you want to show that your project is formatted with Runic you can add the following badge in the repository README:
[](https://github.com/fredrikekre/Runic.jl)
Runic has a check-mode that verifies whether files are correctly formatted or not. This mode
is enabled with the --check flag. In check mode Runic will exit with a non-zero code if
any of the input files are incorrectly formatted. As an example, the following invocation
can be used:
git ls-files -z -- '*.jl' | xargs -0 --no-run-if-empty runic --check --diff
This will run Runic's check mode (--check) on all .jl files in the repository and print
the diff (--diff) if the files are not formatted correctly. If any file is incorrectly
formatted the exit code will be non-zero.
You can use fredrikekre/runic-action to run
Runic on Github Actions:
name: Runic formatting
on:
push:
branches:
- 'master'
- 'release-'
tags:
- '*'
pull_request:
jobs:
runic:
name: Runic
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# - uses: julia-actions/setup-julia@v2
# with:
# version: '1'
# - uses: julia-actions/cache@v2
- uses: fredrikekre/runic-action@v1
with:
version: '1'
See fredrikekre/runic-action for details.
[!IMPORTANT] Please be aware of Runic's version policy when configuring the version. Pinning to a major release (as above with
version: '1') may cause occasional CI failures whenever there is a new minor release of Runic that happens to impact your code base. When this happens you simply have to i) re-run Runic on the new version, ii) commit the result, and iii) add the commit to the ignore list. This is still recommended since minor releases should be relatively rare, and if you use Runic you presumably want these minor bugfixes to be applied to your code base. The alternative is to pin to a minor version and manually upgrade to new minor versions.
The following setup can be used to post any formatting changes as review comments on Github
pull requests. This passses format_files: true to tell Runic to format the files and leave
the diff behind, and then uses
reviewdog/action-suggester to post the diff
as comments. You need to configure continue-on-error for runic-action and then configure
reviewdog/action-suggester (or whatever other follow up action you are using) to fail
instead. For reviewdog/action-suggester this is done by setting fail_level as in the
example below.
name: Runic formatting
on:
push:
branches:
- 'master'
- 'release-'
tags:
- '*'
pull_request:
jobs:
runic:
name: Runic
runs-on: ubuntu-latest
# Permissions needed for reviewdog/action-suggester to post comments
permissions:
contents: read
checks: write
issues: write
pull-requests: write
steps:
- uses: actions/checkout@v4
# - uses: julia-actions/setup-julia@v2
# with:
# version: '1'
# - uses: julia-actions/cache@v2
- uses: fredrikekre/runic-action@v1
with:
version: '1'
format_files: true
# Fail on next step instead
continue-on-error: ${{ github.event_name == 'pull_request' }}
- uses: reviewdog/action-suggester@v1
if: github.event_name == 'pull_request'
with:
tool_name: Runic
fail_level: warning
Runic can be used together with pre-commit using
fredrikekre/runic-pre-commit. After
installing pre-commit you can add the following to your .pre-commit-config.yaml to run
Runic before each commit:
repos:
- repo: https://github.com/fredrikekre/runic-pre-commit
rev: v2.0.1
hooks:
- id: runic
See fredrikekre/runic-pre-commit for
the latest version and more details.
If you don't want to use pre-commit you can also use a plain git hook. Here is an example
hook (.git/hooks/pre-commit):
#!/usr/bin/env bash
# Redirect output to stderr.
exec 1>&2
# Run Runic on added and modified files
git diff-index -z --name-only --diff-filter=AM master | \
grep -z '\.jl$' | \
xargs -0 --no-run-if-empty runic --check --diff
Runic adheres to Semantic Versioning. Semantic versioning is easy to apply and understand when it comes to the API (e.g. the CLI and public methods of the Runic library) but it is less clear how to apply it to changes in the formatted output. Runic makes the following policy:
1.0.x and 1.0.(x + 1). Patch releases are therefore
limited to fixing bugs that caused the formatter to error.This is a list of things that Runic currently is doing:
returnin instead of ∈ and =whereIt is possible to toggle formatting around expressions where you want to disable Runic's formatting. This can be useful in cases where manual formatting increase the readability of the code. For example, manually aligned array literals may look worse when formatted by Runic.
The source comments # runic: off and # runic: on will toggle the formatting off and on,
respectively. The comments must be on their own line, they must be on the same level in the
syntax tree, and they must come in pairs. An exception to the pairing rule is made at top
level where a # runic: off comment will disable formatting for the remainder of the file.
This is so that a full file can be excluded from formatting without having to add a
# runic: on comment at the end of the file.
[!NOTE] Note that it is enough that a comment contain the substring
# runic: offor# runic: onso that they can be combined with other "pragmas" such as e.g. Literate.jl line filters like#src.
[!NOTE] For compatibility with JuliaFormatter the comments
#! format: offand#! format: onare also recognized by Runic.
For example, the following code will toggle off the formatting for the array literal A:
function foo()
a = rand(2)
# runic: off
A = [
-1.00 1.41
3.14 -4.05
]
# runic: on
return A * a
end
No. Use your Enter key or refactor your code.
The body of blocklike expressions (e.g. if, for, while, function, struct, etc.)
always start and end with a newline. Examples:
-if c x end
+if c
+ x
+end
-function f(x) x^2 end
+function f(x)
+ x^2
+end
An exception is made for empty blocks so that e.g.
struct A end
is allowed.
Consistently four spaces for each indentation level.
Standard code blocks (function, for, while, ...) all increase the indentation level by
one until the closing end. Examples:
function f()
- for i in 1:2
- # loop
- end
- while rand() < 0.5
- # loop
- end
+ for i in 1:2
+ # loop
+ end
+ while rand() < 0.5
+ # loop
+ end
end
Listlike expressions like e.g. tuples, function calls, array literals, etc. also increase the indentation level by one until the closing token. This only has an effect if the list span multiple lines. Examples:
x = (
- a, b, c, d,
- e, f, g, h,
+ a, b, c, d,
+ e, f, g, h,
)
foo(
- a, b, c, d,
- e, f, g, h,
+ a, b, c, d,
+ e, f, g, h,
)
[
- a, b, c, d,
- e, f, g, h,
+ a, b, c, d,
+ e, f, g, h,
]
The examples above both result in "hard" indentation levels. Other expressions that span multiple lines result in "soft" indentation levels. The difference between the two is that soft indentation levels don't nest (this is really only applicable to multiline operator call chains).
using Foo:
- foo, bar
+ foo, bar
x = a + b +
- c
+ c
x = a ? b :
- c
+ c
Without soft indentation levels operators chains can result in ugly (but logically correct) indentation levels. For example, the following code:
x = a + b *
c +
d
would be "correct". Such a chain looks better the way it is currently formatted:
x = a + b *
c +
d
returnExplicit return statements are ensured in function and macro definitions by adding
return in front of the last expression, with some exceptions listed below.
for or while loop (which both always evaluate to
nothing) return is added after the loop.if or try block the return is only added in case
there is no return inside any of the branches.let or begin block the return is only added in case
there is no return inside the block.return is only added in case there is no
return inside the macro.return is added in short form functions (f(...) = ...), short form anonymous
functions ((...) -> ...), and do-blocks (f(...) do ...; ...; end).throw
or error, no return is added. This is because it is already obvious that these calls
terminate the function and don't return any value. The same applies if the last
expression is an if, let, begin, or ternary (?:) expression where all branches
terminate with such a call (for if this requires a trailing else since otherwise
there is a fall-through path that returns normally).Note that adding return changes the expression in a way that is visible to macros.
Therefore it is, in general, not valid to add return to a function defined inside a macro
since it isn't possible to know what the macro will expand to. For this reason this
formatting rule is disabled for functions defined inside macros with the exception of some
known and safe ones from Base (e.g. @inline, @generated, ...).
For the same reason mentioned above, if the last expression in a function is a macro call it
isn't valid to step in and add return inside. Instead the return will be added in front
of the macro call like any other expression (unless there is already a return inside of
the macro as described above).
Examples:
function f(n)
- sum(rand(n))
+ return sum(rand(n))
end
macro m(args...)
- :(generate_expr(args...))
+ return :(generate_expr(args...))
end
if or try block it might be better to
recurse into the branches and add return there. Looking at real code, if a
function ends with an if block, it seems about 50/50 whether adding return
after the block or adding return inside the branches is the best choice.
Quite often return if is not the best but at least Runic's current
formatting will force to think about the return value.
See issue #52.Runic formats spaces around infix operators, assignments, comparison chains, and type
comparisons (binary <: and >:), and some other operator-like things. If the space is
missing it will be inserted, if there are multiple spaces it will be reduced to one.
Examples:
-1+2*3
-1 + 2 * 3
+1 + 2 * 3
+1 + 2 * 3
-x=1
-x=+1
-x+=1
-x.+=1
+x = 1
+x = +1
+x += 1
+x .+= 1
-1<2>3
-1 < 2 > 3
+1 < 2 > 3
+1 < 2 > 3
-T<:Integer
-T >: Integer
+T <: Integer
+T >: Integer
-x->x
-a ? b : c
+x -> x
+a ? b : c
Note that since Runic's rules are applied consistently, no matter the context or surrounding code, the "spaces around assignment" rule also means that there will be spaces in keyword arguments in function definitions and calls. Examples:
-foo(; a=1) = a
-foo(a=1)
+foo(; a = 1) = a
+foo(a = 1)
Exceptions to the rule above are :, ^, ::, and unary <: and >:. These are
formatted without spaces around them. Examples:
-a : b
+a:b
-a ^ 5
+a^5
-a :: Int
+a::Int
-<: Integer
->: Integer
+<:Integer
+>:Integer
Consistently use single space around keywords. Examples:
-struct Foo
+struct Foo
-mutable struct Bar
+mutable struct Bar
-function foo(x::T) where {T}
+function foo(x::T) where {T}
Listlike expressions (tuples, function calls/definitions, array literals, etc.) that already span multiple lines are formatted to consistently have a leading and a trailing newline. Trailing commas are enforced for array/tuple literals (where adding another item is common) but optional for function/macro calls/definitions.
-(a,
- b)
+(
+ a,
+ b,
+)
-foo(a,
- b)
+foo(
+ a,
+ b
+)
-[1 2
- 3 4]
+[
+ 1 2
+ 3 4
+]
Note that currently there is no line-length limit employed so expressions that only take up a single line, even if they are long, are not formatted like the above. Thus, only expressions where the original author have "committed" to mulitples lines are affected by this rule.
Listlike expressions (tuples, function calls/definitions, array literals, etc.) use a
consistent rule of no space before , and a single space after ,. Trailing commas are
enforced for array/tuple literals (where adding another item is common) but optional for
function/macro calls/definitions. Leading/trailing spaces are removed. Examples:
-f(a,b)
-(a,b)
-[a, b]
+f(a, b)
+(a, b)
+[a, b]
-(a,b,)
+(a, b)
(
a,
- b
+ b,
)
-( a, b )
+(a, b)
Trailing semicolons are removed in the body of blocklike expressions. Examples
function f(x)
- y = x^2;
- z = y^2; # z = x^4
- return z;
+ y = x^2
+ z = y^2 # z = x^4
+ return z
end
Trailing semicolons at top level and module level are kept since they are sometimes used there for output suppression (e.g. Documenter examples or scripts that are copy-pasted/included in the REPL).
Floating point literals are normalized so that they:
e instead of E for the exponentExamples:
-1.
-.1
+1.0
+0.1
-01.2
-1.0e01
-0.10
+1.2
+1.0e1
+0.1
-1.2E5
+1.2e5
+ for the exponent part, i.e. 1.0e+1 instead of 1.0e1.
Discussed in issue #13.1.00 to 1.0.
Such trailing zeros are sometimes used to align numbers in literal array expressions.
Discussed in issue #14.Hex literals are padded with zeros to better highlight the resulting type of the literal:
UInt8 to 2 characters, UInt16 to 4 characters, UInt32 to 8 characters etc. Examples:
-0x1
-0x123
-0x12345
+0x01
+0x0123
+0x00012345
Add parentheses around operator calls in colon expressions to better highlight the low
precedence of :. Examples:
-1 + 2:3 * 4
-1 + 2:3
-1:3 * 4
+(1 + 2):(3 * 4)
+(1 + 2):3
+1:(3 * 4)
in instead of ∈ and =The keyword in is used consistently instead of ∈ and = in for loops. Examples:
-for i = 1:2
+for i in 1:2
-for i ∈ 1:2
+for i in 1:2
Note that ∈ not replaced when used as an operator outside of loop contexts in
order to be symmetric with ∉ which doesn't have a direct ASCII equivalent.
See #17 for more details.
whereBraces are consistently used around the right hand side of where expressions. Examples:
-T where T
-T where T <: S where S <: Any
+T where {T}
+T where {T <: S} where {S <: Any}
Trailing spaces are removed in code and comments (but not inside of multiline strings where doing so would change the meaning of the code). Examples:
-1 + 1
+1 + 1
-x = 2 # x is two
+x = 2 # x is two
-
+
Tabs are replaced with spaces. Example:
-function f()
- return 1
-end
+function f()
+ return 1
+end
Extra vertical spacing is trimmed so that there are at maximum two empty lines between expressions. Examples:
-function f()
- x = 1
-
-
-
- return x
-end
+function f()
+ x = 1
+
+
+ return x
+end
Any newlines at the start of a file are removed and if the file ends with more than one newline the extra ones are removed.
Julia
94.3%
Python
5.3%
A code formatter with rules set in stone.
Runic is a formatter for the Julia programming language built on top of JuliaSyntax.jl.
Similarly to gofmt, Runic have no configuration. The
formatting rules are set in stone (although not yet complete). This approach is something
that is appreciated by most Go programmers, see for example the following
quote:
Gofmt's style is no one's favorite, yet gofmt is everyone's favorite.
In Julia v1.12 and later, Runic can be installed as a Pkg app:
julia -e 'using Pkg; Pkg.Apps.add("Runic")'
Assuming ~/.julia/bin is in your PATH you can now invoke runic from your shell, e.g.:
runic --version # Show version info
runic --help # Show documentation
# Format all files in-place in the current directory (recursively)
runic --inplace .
[!NOTE] When walking directories Runic refuses to recurse into your home directory and into Julia depot subdirectories (e.g.
~/.julia/packages), and skips nested git repositories (e.g. git submodules). Pass--recurseto include nested git repositories and--force-recurseto disable all checks.
The following copy-pasteable snippet installs Runic (in a @runic shared environment) and
the runic and git-runic wrappers (in .local/bin).
# Install Runic
julia --project=@runic --startup-file=no -e 'using Pkg; Pkg.add("Runic")'
# Install the runic shell script
curl -fsSL -o ~/.local/bin/runic https://raw.githubusercontent.com/fredrikekre/Runic.jl/refs/heads/master/bin/runic
chmod +x ~/.local/bin/runic
# Install the git-runic shell script
curl -fsSL -o ~/.local/bin/git-runic https://raw.githubusercontent.com/fredrikekre/Runic.jl/refs/heads/master/bin/git-runic
chmod +x ~/.local/bin/git-runic
The CLI is the main interface to Runic. runic --help will show all available options
(output included below). Some example invocations are listed here.
Format a single file in place:
runic --inplace file.jl
Format all files in a directory (recursively) in place:
runic --inplace src/
Verify formatting of all files in a directory with verbose and diff output:
runic --check --diff --verbose src/
Format the content of standard in and print the result to standard out:
echo "1+1" | runic
Format Julia code blocks inside docstrings of a Julia source file
(fenced ```julia, ```julia-repl, and ```jldoctest
blocks, plus indented-code blocks — which covers leading method signatures):
runic --docstrings --inplace src/
Format Julia code blocks inside Markdown files. Dispatch is by file extension — any
file whose name ends in .md or .qmd (Quarto) is routed through the Markdown
formatter, which only rewrites the embedded Julia code blocks and leaves the prose
alone. In addition to the fences listed above, Quarto executable code cells
(```{julia}) are recognized as Julia code blocks:
runic --inplace README.md # explicit path — extension handles it
runic --extensions=jl,md,qmd --inplace . # directory walk picks up all three kinds
echo '```julia' > foo.md && echo '1+1' >> foo.md && echo '```' >> foo.md
cat foo.md | runic --stdin-filename=foo.md # stdin dispatch via virtual filename
Output of runic --help for a complete list of options:
$ runic --help
NAME
Runic.main - format Julia source code
SYNOPSIS
julia -m Runic [<options>] <path>...
DESCRIPTION
`Runic.main` (typically invoked as `julia -m Runic`) formats Julia source
code using the Runic.jl formatter.
OPTIONS
<path>...
Input path(s) (files and/or directories) to process. For directories,
all files matching `--extensions` are collected recursively.
If no path is given, or if path is `-`, input is read from stdin.
-c, --check
Do not write output and exit with a non-zero code if the input is not
formatted correctly.
-d, --diff
Print the diff between the input and formatted output to stderr.
Requires `git` to be installed.
--docstrings
Format code blocks in docstrings embedded in source files.
--extensions=<ext>[,<ext>...]
Comma-separated list of file extensions to collect when walking
directories. Defaults to `jl`. Use e.g. `--extensions=jl,md` to
pick up both Julia and Markdown files. Explicit file paths bypass this
filter.
--force-recurse
Disable the safety checks that prevent formatting of the home
directory and Julia depot subdirectories (e.g. `~/.julia/packages`).
Implies `--recurse`.
--help
Print this message.
-i, --inplace
Format files in place.
--lines=<start line>:<end line>
Limit formatting to the line range <start line> to <end line>. Multiple
ranges can be formatted by specifying multiple --lines arguments.
-o <file>, --output=<file>
File to write formatted output to. If no output is given, or if the file
is `-`, output is written to stdout.
--recurse
Recurse into nested git repositories (e.g. git submodules and vendored
clones) when walking directories. By default nested git repositories
are skipped.
--stdin-filename=<filename>
Assumed filename when formatting from stdin. Used for error messages
and for inferring whether input is Julia or Markdown.
-v, --verbose
Enable verbose output.
--version
Print Runic and julia version information.
In addition to the CLI there is also the two function Runic.format_file and
Runic.format_string. See their respective docstrings for details.
Most code editors have code formatting capabilities and many can be configured to use Runic. Example configuration for some editors are given in the following sections.
[!NOTE] These editor configurations assume you have installed Runic as an app (as suggested in the Installation section above) and that the
runicbinary is available in the PATH environment variable that your editor is seeing. If the editor doesn't find theruniccommand you can replacerunicwith the full path to the app instead (e.g./home/sauron/.julia/bin/runic).
[!IMPORTANT] Note that these configurations depend on third party plugins. They works as advertised but use it at your own risk.
Runic can be used as a formatter in Neovim using conform.nvim. Refer to the conform.nvim repository for installation and setup instructions.
Runic is available directly in conform so the only thing that needs to be done is to configure conform to use runic for Julia files:
require("conform").setup({
formatters_by_ft = {
julia = {"runic"},
},
})
Note that conform (and thus Runic) can be used as formatexpr for the gq command. This is
enabled by adding the following to your configuration:
vim.o.formatexpr = "v:lua.require('conform').formatexpr()"
Runic can be used as a formatter in VS Code using the extension Custom Local Formatters.
After installing the extension you can configure Runic as a local formatter by adding the
following entry to your settings.json.
"customLocalFormatters.formatters": [
{
"command": "runic",
"languages": ["julia"]
}
]
Using the "Format Document" VS Code command will now format the file using Runic. Note that the first time you execute the command you will be prompted to select a formatter since the Julia language extension also comes with a formatter.
Runic can be used as a formatter in Emacs using apheleia. Refer to the apheleia repository for installation and setup instruction.
Runic is not (yet) available directly in apheleia so the
following configuration needs to be added to your .emacs.
(push `(runic . ("runic")) apheleia-formatters)
(push '(julia-mode . runic) apheleia-mode-alist)
Runic can be used as a formatter in Helix. Configure Helix's
:format command to use Runic for julia code by adding the following to the
languages.toml configuration file.
[[language]]
name = "julia"
auto-format = false
formatter = { command = "runic" }
Runic can be used as a formatter in Zed. You can configure Runic as a
local formatter by adding the following entry to your settings.json.
"languages": {
"Julia": {
"formatter": {
"external": {
"command": "runic",
}
}
}
}
The git-runic
script (a modified version of
git-clang-format)
provides a convenient way to apply Runic formatting incrementally to a code
base by limiting formatting to lines that are added or modified in each commit.
The script can be installed with the following commands (replace the two
occurrences of ~/.local/bin if needed):
# Download the script into ~/.local/bin
curl -fsSL -o ~/.local/bin/git-runic https://raw.githubusercontent.com/fredrikekre/Runic.jl/refs/heads/master/bin/git-runic
# Make the script executable
chmod +x ~/.local/bin/git-runic
# Verify the installation
git runic -h
Here is a checklist for adopting Runic formatting wholesale in a project:
runic -i <path> and commit the changes in separate
commit. This commit can be ignored in git blame (see Ignore formatting commits in git
blame).Alternatively Runic formatting can be adopted incrementally by using the
git-runic integration, see Git integration for details.
When setting up Runic formatting for a repository for the first time (or when upgrading to a
new version of Runic) the formatting commit will likely result in a large diff with mostly
non functional changes such as e.g. whitespace. Since the diff is large it is likely that it
will show up and interfere when using git-blame. To
ignore commits during git-blame you can i) add them to a file .git-blame-ignore-revs and
ii) tell git to use this file as ignore file by running
git config blame.ignoreRevsFile .git-blame-ignore-revs
See the git-blame documentation for details.
For example, such a file may look like this:
# Adding Runic formatting
<commit hash of formatting commit>
# Upgrading Runic from 1.0 to 2.0
<commit hash of formatting commit>
If you want to show that your project is formatted with Runic you can add the following badge in the repository README:
[](https://github.com/fredrikekre/Runic.jl)
Runic has a check-mode that verifies whether files are correctly formatted or not. This mode
is enabled with the --check flag. In check mode Runic will exit with a non-zero code if
any of the input files are incorrectly formatted. As an example, the following invocation
can be used:
git ls-files -z -- '*.jl' | xargs -0 --no-run-if-empty runic --check --diff
This will run Runic's check mode (--check) on all .jl files in the repository and print
the diff (--diff) if the files are not formatted correctly. If any file is incorrectly
formatted the exit code will be non-zero.
You can use fredrikekre/runic-action to run
Runic on Github Actions:
name: Runic formatting
on:
push:
branches:
- 'master'
- 'release-'
tags:
- '*'
pull_request:
jobs:
runic:
name: Runic
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# - uses: julia-actions/setup-julia@v2
# with:
# version: '1'
# - uses: julia-actions/cache@v2
- uses: fredrikekre/runic-action@v1
with:
version: '1'
See fredrikekre/runic-action for details.
[!IMPORTANT] Please be aware of Runic's version policy when configuring the version. Pinning to a major release (as above with
version: '1') may cause occasional CI failures whenever there is a new minor release of Runic that happens to impact your code base. When this happens you simply have to i) re-run Runic on the new version, ii) commit the result, and iii) add the commit to the ignore list. This is still recommended since minor releases should be relatively rare, and if you use Runic you presumably want these minor bugfixes to be applied to your code base. The alternative is to pin to a minor version and manually upgrade to new minor versions.
The following setup can be used to post any formatting changes as review comments on Github
pull requests. This passses format_files: true to tell Runic to format the files and leave
the diff behind, and then uses
reviewdog/action-suggester to post the diff
as comments. You need to configure continue-on-error for runic-action and then configure
reviewdog/action-suggester (or whatever other follow up action you are using) to fail
instead. For reviewdog/action-suggester this is done by setting fail_level as in the
example below.
name: Runic formatting
on:
push:
branches:
- 'master'
- 'release-'
tags:
- '*'
pull_request:
jobs:
runic:
name: Runic
runs-on: ubuntu-latest
# Permissions needed for reviewdog/action-suggester to post comments
permissions:
contents: read
checks: write
issues: write
pull-requests: write
steps:
- uses: actions/checkout@v4
# - uses: julia-actions/setup-julia@v2
# with:
# version: '1'
# - uses: julia-actions/cache@v2
- uses: fredrikekre/runic-action@v1
with:
version: '1'
format_files: true
# Fail on next step instead
continue-on-error: ${{ github.event_name == 'pull_request' }}
- uses: reviewdog/action-suggester@v1
if: github.event_name == 'pull_request'
with:
tool_name: Runic
fail_level: warning
Runic can be used together with pre-commit using
fredrikekre/runic-pre-commit. After
installing pre-commit you can add the following to your .pre-commit-config.yaml to run
Runic before each commit:
repos:
- repo: https://github.com/fredrikekre/runic-pre-commit
rev: v2.0.1
hooks:
- id: runic
See fredrikekre/runic-pre-commit for
the latest version and more details.
If you don't want to use pre-commit you can also use a plain git hook. Here is an example
hook (.git/hooks/pre-commit):
#!/usr/bin/env bash
# Redirect output to stderr.
exec 1>&2
# Run Runic on added and modified files
git diff-index -z --name-only --diff-filter=AM master | \
grep -z '\.jl$' | \
xargs -0 --no-run-if-empty runic --check --diff
Runic adheres to Semantic Versioning. Semantic versioning is easy to apply and understand when it comes to the API (e.g. the CLI and public methods of the Runic library) but it is less clear how to apply it to changes in the formatted output. Runic makes the following policy:
1.0.x and 1.0.(x + 1). Patch releases are therefore
limited to fixing bugs that caused the formatter to error.This is a list of things that Runic currently is doing:
returnin instead of ∈ and =whereIt is possible to toggle formatting around expressions where you want to disable Runic's formatting. This can be useful in cases where manual formatting increase the readability of the code. For example, manually aligned array literals may look worse when formatted by Runic.
The source comments # runic: off and # runic: on will toggle the formatting off and on,
respectively. The comments must be on their own line, they must be on the same level in the
syntax tree, and they must come in pairs. An exception to the pairing rule is made at top
level where a # runic: off comment will disable formatting for the remainder of the file.
This is so that a full file can be excluded from formatting without having to add a
# runic: on comment at the end of the file.
[!NOTE] Note that it is enough that a comment contain the substring
# runic: offor# runic: onso that they can be combined with other "pragmas" such as e.g. Literate.jl line filters like#src.
[!NOTE] For compatibility with JuliaFormatter the comments
#! format: offand#! format: onare also recognized by Runic.
For example, the following code will toggle off the formatting for the array literal A:
function foo()
a = rand(2)
# runic: off
A = [
-1.00 1.41
3.14 -4.05
]
# runic: on
return A * a
end
No. Use your Enter key or refactor your code.
The body of blocklike expressions (e.g. if, for, while, function, struct, etc.)
always start and end with a newline. Examples:
-if c x end
+if c
+ x
+end
-function f(x) x^2 end
+function f(x)
+ x^2
+end
An exception is made for empty blocks so that e.g.
struct A end
is allowed.
Consistently four spaces for each indentation level.
Standard code blocks (function, for, while, ...) all increase the indentation level by
one until the closing end. Examples:
function f()
- for i in 1:2
- # loop
- end
- while rand() < 0.5
- # loop
- end
+ for i in 1:2
+ # loop
+ end
+ while rand() < 0.5
+ # loop
+ end
end
Listlike expressions like e.g. tuples, function calls, array literals, etc. also increase the indentation level by one until the closing token. This only has an effect if the list span multiple lines. Examples:
x = (
- a, b, c, d,
- e, f, g, h,
+ a, b, c, d,
+ e, f, g, h,
)
foo(
- a, b, c, d,
- e, f, g, h,
+ a, b, c, d,
+ e, f, g, h,
)
[
- a, b, c, d,
- e, f, g, h,
+ a, b, c, d,
+ e, f, g, h,
]
The examples above both result in "hard" indentation levels. Other expressions that span multiple lines result in "soft" indentation levels. The difference between the two is that soft indentation levels don't nest (this is really only applicable to multiline operator call chains).
using Foo:
- foo, bar
+ foo, bar
x = a + b +
- c
+ c
x = a ? b :
- c
+ c
Without soft indentation levels operators chains can result in ugly (but logically correct) indentation levels. For example, the following code:
x = a + b *
c +
d
would be "correct". Such a chain looks better the way it is currently formatted:
x = a + b *
c +
d
returnExplicit return statements are ensured in function and macro definitions by adding
return in front of the last expression, with some exceptions listed below.
for or while loop (which both always evaluate to
nothing) return is added after the loop.if or try block the return is only added in case
there is no return inside any of the branches.let or begin block the return is only added in case
there is no return inside the block.return is only added in case there is no
return inside the macro.return is added in short form functions (f(...) = ...), short form anonymous
functions ((...) -> ...), and do-blocks (f(...) do ...; ...; end).throw
or error, no return is added. This is because it is already obvious that these calls
terminate the function and don't return any value. The same applies if the last
expression is an if, let, begin, or ternary (?:) expression where all branches
terminate with such a call (for if this requires a trailing else since otherwise
there is a fall-through path that returns normally).Note that adding return changes the expression in a way that is visible to macros.
Therefore it is, in general, not valid to add return to a function defined inside a macro
since it isn't possible to know what the macro will expand to. For this reason this
formatting rule is disabled for functions defined inside macros with the exception of some
known and safe ones from Base (e.g. @inline, @generated, ...).
For the same reason mentioned above, if the last expression in a function is a macro call it
isn't valid to step in and add return inside. Instead the return will be added in front
of the macro call like any other expression (unless there is already a return inside of
the macro as described above).
Examples:
function f(n)
- sum(rand(n))
+ return sum(rand(n))
end
macro m(args...)
- :(generate_expr(args...))
+ return :(generate_expr(args...))
end
if or try block it might be better to
recurse into the branches and add return there. Looking at real code, if a
function ends with an if block, it seems about 50/50 whether adding return
after the block or adding return inside the branches is the best choice.
Quite often return if is not the best but at least Runic's current
formatting will force to think about the return value.
See issue #52.Runic formats spaces around infix operators, assignments, comparison chains, and type
comparisons (binary <: and >:), and some other operator-like things. If the space is
missing it will be inserted, if there are multiple spaces it will be reduced to one.
Examples:
-1+2*3
-1 + 2 * 3
+1 + 2 * 3
+1 + 2 * 3
-x=1
-x=+1
-x+=1
-x.+=1
+x = 1
+x = +1
+x += 1
+x .+= 1
-1<2>3
-1 < 2 > 3
+1 < 2 > 3
+1 < 2 > 3
-T<:Integer
-T >: Integer
+T <: Integer
+T >: Integer
-x->x
-a ? b : c
+x -> x
+a ? b : c
Note that since Runic's rules are applied consistently, no matter the context or surrounding code, the "spaces around assignment" rule also means that there will be spaces in keyword arguments in function definitions and calls. Examples:
-foo(; a=1) = a
-foo(a=1)
+foo(; a = 1) = a
+foo(a = 1)
Exceptions to the rule above are :, ^, ::, and unary <: and >:. These are
formatted without spaces around them. Examples:
-a : b
+a:b
-a ^ 5
+a^5
-a :: Int
+a::Int
-<: Integer
->: Integer
+<:Integer
+>:Integer
Consistently use single space around keywords. Examples:
-struct Foo
+struct Foo
-mutable struct Bar
+mutable struct Bar
-function foo(x::T) where {T}
+function foo(x::T) where {T}
Listlike expressions (tuples, function calls/definitions, array literals, etc.) that already span multiple lines are formatted to consistently have a leading and a trailing newline. Trailing commas are enforced for array/tuple literals (where adding another item is common) but optional for function/macro calls/definitions.
-(a,
- b)
+(
+ a,
+ b,
+)
-foo(a,
- b)
+foo(
+ a,
+ b
+)
-[1 2
- 3 4]
+[
+ 1 2
+ 3 4
+]
Note that currently there is no line-length limit employed so expressions that only take up a single line, even if they are long, are not formatted like the above. Thus, only expressions where the original author have "committed" to mulitples lines are affected by this rule.
Listlike expressions (tuples, function calls/definitions, array literals, etc.) use a
consistent rule of no space before , and a single space after ,. Trailing commas are
enforced for array/tuple literals (where adding another item is common) but optional for
function/macro calls/definitions. Leading/trailing spaces are removed. Examples:
-f(a,b)
-(a,b)
-[a, b]
+f(a, b)
+(a, b)
+[a, b]
-(a,b,)
+(a, b)
(
a,
- b
+ b,
)
-( a, b )
+(a, b)
Trailing semicolons are removed in the body of blocklike expressions. Examples
function f(x)
- y = x^2;
- z = y^2; # z = x^4
- return z;
+ y = x^2
+ z = y^2 # z = x^4
+ return z
end
Trailing semicolons at top level and module level are kept since they are sometimes used there for output suppression (e.g. Documenter examples or scripts that are copy-pasted/included in the REPL).
Floating point literals are normalized so that they:
e instead of E for the exponentExamples:
-1.
-.1
+1.0
+0.1
-01.2
-1.0e01
-0.10
+1.2
+1.0e1
+0.1
-1.2E5
+1.2e5
+ for the exponent part, i.e. 1.0e+1 instead of 1.0e1.
Discussed in issue #13.1.00 to 1.0.
Such trailing zeros are sometimes used to align numbers in literal array expressions.
Discussed in issue #14.Hex literals are padded with zeros to better highlight the resulting type of the literal:
UInt8 to 2 characters, UInt16 to 4 characters, UInt32 to 8 characters etc. Examples:
-0x1
-0x123
-0x12345
+0x01
+0x0123
+0x00012345
Add parentheses around operator calls in colon expressions to better highlight the low
precedence of :. Examples:
-1 + 2:3 * 4
-1 + 2:3
-1:3 * 4
+(1 + 2):(3 * 4)
+(1 + 2):3
+1:(3 * 4)
in instead of ∈ and =The keyword in is used consistently instead of ∈ and = in for loops. Examples:
-for i = 1:2
+for i in 1:2
-for i ∈ 1:2
+for i in 1:2
Note that ∈ not replaced when used as an operator outside of loop contexts in
order to be symmetric with ∉ which doesn't have a direct ASCII equivalent.
See #17 for more details.
whereBraces are consistently used around the right hand side of where expressions. Examples:
-T where T
-T where T <: S where S <: Any
+T where {T}
+T where {T <: S} where {S <: Any}
Trailing spaces are removed in code and comments (but not inside of multiline strings where doing so would change the meaning of the code). Examples:
-1 + 1
+1 + 1
-x = 2 # x is two
+x = 2 # x is two
-
+
Tabs are replaced with spaces. Example:
-function f()
- return 1
-end
+function f()
+ return 1
+end
Extra vertical spacing is trimmed so that there are at maximum two empty lines between expressions. Examples:
-function f()
- x = 1
-
-
-
- return x
-end
+function f()
+ x = 1
+
+
+ return x
+end
Any newlines at the start of a file are removed and if the file ends with more than one newline the extra ones are removed.
Julia
94.3%
Python
5.3%