zift Wiki

This page documents every configuration option supported by zift today, with small examples.

The full config is:

.{
    .indent = .{ .spaces = 4 },
    .single_item_whitespace = false,
    .indent_switch_cases = true,
    .braces = .{
        .conditionals = .default,
        .labeled = .default,
        .loops = .default,
        .functions = .default,
        .switches = .default,
        .types = .default,
    },
    .alignment = .{
        .comments = 0,
        .initializers = 0,
        .declarations = 0,
        .prongs = 0,
        .enums = 0,
    },
}

Differences with `zig fmt`

zift mostly matches zig fmt by default, with some exceptions.

Note that these won't alter code formatted by zig fmt on their own, they only preserve differences if already there.

line break after struct initializer

If a struct initializer field value is already written on the next line after =, zift keeps that line break instead of collapsing the assignment back to one line.

For example, this is allowed:

const value = .{
    .x_rotation_deg =
        -@as(f32, 1) / 2,
};

line break after return statement

If a return statement is followed by a line break, this is preserved, instead of the returned value being brought to the same line of the return statement.

For example, this is allowed:

    return
        cfg.alignment.comments > 0 or
        cfg.alignment.initializers > 0 or
        cfg.alignment.declarations > 0 or
        cfg.alignment.prongs > 0;

line break before `orelse` or `catch`

Line breaks are preserved also before orelse expressions.

For example, this is allowed:

const range =
    findMotionRange(e, motion)
    orelse return .fail;

Same is true for catch:

const range =
    findMotionRange(e, motion)
    catch return .fail;

line break after switch prong arrow

Line breaks are preserved also after => in switch prongs. If the arrow is followd by a capture, that is kept on the line with the arrow.

For example, this is allowed:

const value = switch (v) {
    0 =>
        .zero,
    1 => |x|
        x,
    else => .other,
};

empty line before `else`

If an else branch is already rendered on its own line, zift keeps one empty line before it when that empty line is already present in the source.

This applies to else if too.

For example, this is allowed:

const end =
    if (vbcur) |br|
        br

    else if (eol)
        1

    else
        2;

indent

Controls the indentation character and amount.

.indent = .tabs

pub fn f() void {
	if (true) {
		return;
	}
}

.indent = .{ .spaces = 2 }

pub fn f() void {
  if (true) {
    return;
  }
}

braces

Controls brace placement by category.

Note that every category can have different settings, so if you like .allman on conditionals/loops and .gnu on switches/labeled blocks, it's perfectly doable.


braces.conditionals

Valid values: .default, .newline_before_else, .allman, .gnu

.conditionals = .default

if (a) {
    work();
} else {
    fallback();
}

.conditionals = .newline_before_else

This setting only affects the placement of else statements, pushing them into their own line. It is consistent with how they look in switch statements.

if (a) {
    work();
}
else {
    fallback();
}

.conditionals = .allman

if (a)
{
    work();
}
else
{
    fallback();
}

.conditionals = .gnu

if (a)
    {
        work();
    }
else
    {
        fallback();
    }


braces.labeled

Valid values: .default, .allman, .gnu

This controls labeled blocks such as blk: { ... }.

When a labeled block appears in an if or else branch:

.labeled = .default

const a = if (v) blk: {
    break :blk 3;
} else blk: {
    break :blk 3;
};

.labeled = .allman

const a = if (v) blk:
{
    break :blk 3;
} else blk:
{
    break :blk 3;
};

With braces.conditionals = .newline_before_else or .allman, only the else placement changes:

const a = if (v) blk:
{
    break :blk 3;
}
else blk:
{
    break :blk 3;
};

.labeled = .gnu

const a = if (v) blk:
    {
        break :blk 3;
    } else blk:
        {
            break :blk 3;
        };

With braces.conditionals = .newline_before_else or .allman:

const a = if (true) blk:
    {
        break :blk 3;
    }
else blk:
    {
        break :blk 3;
    };

or with if on its own line:

const a =
    if (true) blk:
        {
            break :blk 3;
        }
    else blk:
        {
            break :blk 3;
        };


braces.loops

Valid values: .default, .allman, .gnu

.loops = .default

while (i < items.len) : (i += 1) {
    work(items[i]);
}

.loops = .allman

while (i < items.len) : (i += 1)
{
    work(items[i]);
}

.loops = .gnu

while (i < items.len) : (i += 1)
    {
        work(items[i]);
    }


braces.functions

Valid values: .default, .allman

.functions = .default

pub fn f() void {
    work();
}

.functions = .allman

pub fn f() void
{
    work();
}


braces.switches

Valid values: .default, .allman, .gnu

.switches = .default

return switch (v) {
    0 => "zero",
    else => "other",
};

.switches = .allman

return switch (v)
{
    0 => "zero",
    else => "other",
};

This also stays aligned when switch is used in an assignment:

const a = switch (v)
{
    0 => "zero",
    else => "other",
};

.switches = .gnu

const a = switch (v)
    {
        0 => "zero",
        else => "other",
    };


braces.types

Valid values: .default, .allman

.types = .default

const Item = struct {
    value: usize,
};

.types = .allman

const Item = struct
{
    value: usize,
};

alignment

These options align only consecutive matching lines.

Threshold rules:

Alignment is table-like on purpose. Runs with mixed indentation are skipped. Runs that contain nested blocks are also skipped.


alignment.comments

Aligns inline // comments.

.comments = 0

const a = 1; // alpha
const long_name = 2; // beta

.comments = 2

const a = 1;         // alpha
const long_name = 2; // beta

.comments = 3

With only two matching lines, nothing changes:

const a = 1; // alpha
const long_name = 2; // beta

With three or more matching lines, they align:

const a = 1;         // alpha
const long_name = 2; // beta
const z = 3;         // gamma


alignment.initializers

Aligns = in flat struct initializer field lists.

Currently, it is only possible to align initializers in single, non-nested blocks. If a nested block is present, or if there are differences in indentation, the block is left alone, no matter the setting.

return .{
    .ctx = .{
        .fd = handle,
        .timeout = 0,
        .reader = reader,
    },
};

.initializers = 0

const value = .{
    .name = name,
    .count = count,
    .kind = .alpha,
};

.initializers = 2

const value = .{
    .name  = name,
    .count = count,
    .kind  = .alpha,
};


alignment.declarations

Aligns = in consecutive const and var declarations.

.declarations = 0

const init: u8 = 3;
const a: u3 = 2;
const b: []const u8 = &.{};

.declarations = 2

const init: u8      = 3;
const a: u3         = 2;
const b: []const u8 = &.{};

.declarations = 4

With only three matching lines, nothing changes:

const init: u8 = 3;
const a: u3 = 2;
const b: []const u8 = &.{};


alignment.prongs

Aligns => in flat switch prong lists.

.prongs = 0

return switch (k) {
    .a => 1,
    .long_name => 2,
    .b => 3,
};

.prongs = 2

return switch (k) {
    .a         => 1,
    .long_name => 2,
    .b         => 3,
};

If one prong opens a block, the whole run is left alone:

return switch (k) {
    .a => 1,
    .long_name => blk: {
        break :blk 2;
    },
    .b => 3,
};


alignment.enums

Aligns = in consecutive enum fields.

.enums = 0

const Signed = enum(i8) {
    minus_one = -1,
    zero = 0,
    one = 1,
};

.enums = 2

const Signed = enum(i8) {
    minus_one = -1,
    zero      = 0,
    one       = 1,
};

single_item_whitespace

Controls spaces inside single-item arrays and tuple-like literals such as .{1}.

.single_item_whitespace = false

const a = .{1};
const b = .{ .x = 1 };

.single_item_whitespace = true

const a = .{ 1 };
const b = .{ .x = 1 };

Only single-item positional literals change. Regular struct literals keep their normal formatting.


indent_switch_cases

Controls whether switch prongs are indented inside the switch body.

.indent_switch_cases = true

switch (v) {
    0 => {},
    else => {},
}

.indent_switch_cases = false

switch (v) {
0 => {},
else => {},
}

Example Configs

Aligned initializers and comments, `else` in own line

.{
    .indent = .{ .spaces = 4 },
    .single_item_whitespace = false,
    .indent_switch_cases = true,
    .braces = .{
        .conditionals = .newline_before_else,
    },
    .alignment = .{
        .comments = 3,
        .initializers = 3,
        .declarations = 0,
        .prongs = 0,
        .enums = 0,
    },
}

Tabs and Allman braces, aligned initializers and comments

.{
    .indent = .tabs,
    .single_item_whitespace = false,
    .indent_switch_cases = true,
    .braces = .{
        .conditionals = .allman,
        .labeled = .allman,
        .loops = .allman,
        .functions = .allman,
        .switches = .allman,
        .types = .allman,
    },
    .alignment = .{
        .comments = 3,
        .initializers = 3,
        .declarations = 0,
        .prongs = 0,
        .enums = 0,
    },
}