Simple code formatter for jai.
To build the formatter simply run jai ./first.jai - release. This will create the jai-format.exe in the build folder.
To format a file run jai-format path/to/your/file.jai. You can format multiple files by passing a space separated list of paths as arguments: jai-format file1.jai ./path/to/folder file3.jai. If a folder is passed as an argument all .jai files will be formated in that folder.
There are some CLI parameters you can use to alter the default behaviour:
-
-config_file: default = "./.jai-format"
Path to a config file that should be used. If not set the formatter will search the current working directory for a .jai-format file. If no config is found the default config will be used. -
-recursive: default = false
If set and there are directories in the argument list all directories will be formatted recursively. -
-silent: default = false
If set no output will be printed other than the final result if -to_stdout is also set. -
-to_file: default = false
If set the source file will be overwritten with the new formatted output. -
-to_stdout: default = false
If set the formatted output will be printed to standard out. -
-verbose: default = false
If set more output information will be printed. -
-help,-HELP,-?: Show the list of commands.
Example usage with some parameters: jai-format -silent -to_stdout -to_file file.jai.
You can also use the formatter as a module by directly using the format_file, and format_from_string functions.
For example:
config := load_config(config_file);
// make_formatter allocates the result buffer and an indentation string using whatever allocator is currently set in context.allocator
// In addition to that it also creates it's own Flat_Pool for other allocations needed during formatting
formatter := make_formatter( config );
status, output := format_file(*formatter, some_file);
// do something with the output
reset_formatter(*formatter); // After this the output is no longer valid. This only resets the Flat_Pool, it doesn't free the memory.
status, output = format_from_string(*formatter, some_code_string);
// do something with the output
reset_formatter(*formatter);
// If you care about freeing all the memory call deinit_formatter
deinit_formatter(*formatter);- spaces_inside_parens: default = false
If true a space will be inserted after(and before)if there is something between them.
true: false:
print( "%\n", "test" ); print("%\n", "test");
- spaces_inside_brackets: default = false
If true a space will be inserted after[and before]in array subscripts. This option doesn't insert spaces inside array literals.
true: false:
a := array[ 0 ]; a := array[0];
- spaces_inside_literals: default = false
If true a space will be inserted inside struct and array literals.
true: false:
a := Struct.{ 1, 2, 3 }; a := Struct.{1, 2, 3};
b := int.[ 1, 2, 3 ]; b := int.[1, 2, 3];
- spaces_around_operators: default = true
If true spaces will be inserted around binary operators.
true: false:
a := 1 + 2; a:=1+2;
- wrap_before_operators: default = false
If true a new line will be inserted before the operator in a multiline expression.
true: false:
if a if a ||
|| b b ||
|| c c
-
alignment_mode: default = EXACT
Specifies how to align expressions after line breaks.
Things that will be aligned are:- Declarations and assignments (
:=,::,=) - Everything surrounded with
(),[]or{} - Expressions after
if,whileandfor
Alowed values are:
DISABLED- no alignmentINDENT_ONLY- useindent_widthas alignmentEXACT- exactly align expressions with their start point
For more alignment examples see
tests/alignment.test.jai. - Declarations and assignments (
DISABLED: INDENT_ONLY: EXACT:
test(a, b, c, test(a, b, c, test(a, b, c,
d(a, b, d(a, b, d(a, b,
c), e); c), e); c), e);
-
alignment mode overrides:
You can override the alignment_mode for specific things:- declaration_alignment_mode - alignment for declarations and assignments (
:=,::,=) - expression_alignment_mode - alignment for expressions after
if,whileandforetc. - parens_alignment_mode - alignment for things surrounded with
() - brackets_alignment_mode - alignment for things surrounded with
[] - struct_literal_alignment_mode - alignment for struct literals (
.{})
If any of these options are not set then the value of
alignment_modewill be used. - declaration_alignment_mode - alignment for declarations and assignments (
-
struct_literal_dot_mode: default = FORCE
Specifies wheter to always add or remove a dot before a struct literal.
FORCE: REMOVE:
a := make_struct(.{1, 2, 3}); a := make_struct({1, 2, 3});
-
single_statement_brace_mode: default = DONT_MODIFY
Specifies wheter to keep, add or remove braces to single statements in control flow expressions (if,for,while).Alowed values are:
DONT_MODIFY- don't modify the bracesFORCE_ADD- always add braces to single line statementsFORCE_REMOVE- always remove braces from single line statements
DONT_MODIFY: FORCE_ADD: FORCE_REMOVE:
if condition { if condition { if condition
print("true"); print("true"); print("true");
} else } else { else
print("false"); print("false"); print("false");
}
-
single_statement_line_mode: default = DONT_MODIFY
Specifies wheter to keep, add or remove new lines to single statements in control flow expressions (if,for,while).Alowed values are:
DONT_MODIFY- don't modify the new linesNEW_LINE- always add new line before single line statementsSAME_LINE- always remove new line before single line statements
DONT_MODIFY: NEW_LINE: SAME_LINE:
if condition if condition if condition print("true");
print("true"); print("true"); else print("false");
else print("false"); else
print("false");
- single_statement_double_space: default = false
If true two spaces will be added before single statements in control flow expressions (if,for,while).
true: false:
if true print("true"); if true print("true");
- braces_on_new_line: default = false
If true opening braces will be put on a new line.
There is one exception to this option. If a opening and closing braces are on the same line the block will be kept as is.
For exampleif true {print("true");}will remain all on one line.
true: false:
if true if true {
{ }
}
- quick_lambda_brace_on_new_line: default = false
If true the opening brace of a quick lambda will be placed on a new line.
This setting also applies to#codeblocks.
This option ignores the braces_on_new_line option.
true: false:
a :: (x, y) => a :: (x, y) => {
{ }
}
- else_on_new_line: default = false
If true the else keyword will be placed on a new line after the closing brace.
true: false:
if true { if true {
} } else {
else { }
}
- indent_case: default = false
If true case statements will be indented one level instead of staying at the same indentation level as the if keyword.
true: false:
if a == { if a == {
case 1; case 1;
case; case;
} }
- surround_multiple_returns_with_parens: default = false
If true multiple returns will always be surrounded with parenthesis.
If this option is set to false the formatter will not remove the parenthesis that are already there.
true: false:
a :: () -> (int, bool) a :: () -> int, bool
- insert_space_in_single_line_comments: default = true
If true a space will be added after the // if there isn't already one.
The formatter will not trim the leading whitespace in the comments.
true: false:
// comment //comment
- keep_whitespace_before_single_line_comments: default = false
If true whitespace before single line comments at the end of the line will be kept as is.
Currently this option only works ifindent_modeis set toSPACES.
true: false:
print("asd"); // aligned comment print("asd"); // comment
print("asdzxc"); // aligned comment print("asdzxc"); // comment
- max_empty_lines_to_keep: default = 1
Maximum amount of empty lines that will be kept when formatting.
Empty lines at the beginning of a scope will be removed regardless of what this option is set to.
For example:
if true { if true {
print("true");
print("true"); ---------> }
}
- space_after_cast: default = false
If true a space will be added after a cast For example:
true: false:
a := cast(u16) 5; a := cast(u16)5;
-
indent_mode : default = SPACES
What character to use for indentation. EitherSPACESorTABS. -
indent_width: default = 4
Amount of spaces or tabs to use as indentation. -
tab_width: default = 4
Set this to the tab width you use in your editor. This option is only used forEXACTalignment mode to allow precise alignment by mixing tabs and spaces.
For example:
____if ( a > 1 && a < 5 ) ||
____...( a > 8 && a < 10 )
^ ^
tab spaces
- no_carriage_return_on_windows: default = false
If true only\nwill be used as a line ending instead of\r\non windows.
To change the configuration create a .jai-format file in the working directory of the formatter and put your options there.
Options should be formatted like this: option_name option_value. You can use # to comment out lines in the config. See the .jai-format file in this repo for an exaxmple configuration.
There are some automated tests that check the correctness of the formatted output. To run them just run jai first.jai - test.
- The biggest limitation right now is that the formatter assumes there are no syntax errors in your code. If you try formatting a file that has syntax errors it may or may not work properly. This is not a big issue if you're using the formatter directly in an editor because even if something goes wrong you can easily undo the changes. But when using the formatter as a cli tool with the
-to_fileparameter proceed with caution. - Comments in weird places will be rearranged. For example:
test :/*asd*/: (param/*asd*/: int) /*zxc*/ -> /*asd*/ int
will be formatted as:
test :: /*asd*/(param: /*asd*/int) -> /*zxc*//*asd*/int
Add this to your conform setup:
local conform_util = require("conform.util")
require("conform").setup({
formatters_by_ft = {
jai = { "jai-format" },
},
formatters = {
["jai-format"] = {
command = "jai-format",
args = { "-silent", "-to_file", "$FILENAME" },
cwd = conform_util.root_file({ ".jai-format" }),
stdin = false,
},
}
})You can run the jai-format command manually:
local function jai_format()
local job = require("plenary.job")
local current_file = vim.fn.expand("%:p")
job:new({
command = "jai-format",
args = { "-to_file", current_file },
cwd = vim.fn.getcwd(),
on_exit = function(j, return_val)
local callback = vim.schedule_wrap(function(file)
vim.api.nvim_command("checktime " .. file)
end)
callback(current_file)
end,
}):start()
end
vim.api.nvim_create_user_command("JaiFormat", jai_format, {nargs = 0, desc = ""})After adding this to your config you can run :JaiFormat to format the current buffer or you can setup an autocommand to format on save:
local autocommand_group = vim.api.nvim_create_augroup('auto_commands', {})
vim.api.nvim_create_autocmd({"BufWritePost"}, {
command = "JaiFormat",
group = autocommand_group,
pattern = {"*.jai"}
})- Write more tests
- Don't format if there are syntax errors. This relies on the jai_parser module checking for syntax errors which it currently doesn't (there's only minimal checking and not all errors are detected).
- Add
//jai-format:offand//jai-format:oncomments that allow disabling the formatter in specific places. - Add
//jai-format:config_option=truecomments that allow overriding configuration options in specific places.