Zig 0.17.0-dev (Split by item)

Shortcut keys
?
Show these tips
S
Focus search field
Esc
Clear focus & close tips
U
Jump to source code
↑
Move up in search results
↓
Move down in search results
↵
Go to active search result
This is an example of documentation generated by ZigDoc, an alternative to Zig's built-in Auto Doc feature. See also examples in other modes/formats. The project being documented here (as the example) is the Zig standard library. Private Struct

AutoIndentingStream

Private Struct
std.zig.Ast.Render.AutoIndentingStream
const AutoIndentingStream = struct

Automatically inserts indentation of written data by keeping track of the current indentation level

We introduce a new indentation scope with pushIndent/popIndent whenever we potentially want to introduce an indent after the next newline.

Indentation should only ever increment by one from one line to the next, no matter how many new indentation scopes are introduced. This is done by only realizing the indentation from the most recent scope. As an example:

while (foo) if (bar) f(x);

The body of while introduces a new indentation scope and the body of if also introduces a new indentation scope. When the newline is seen, only the indentation scope of the if is realized, and the while is not.

As comments are rendered during space rendering, we need to keep track of the appropriate indentation level for them with pushSpace/popSpace. This should be done whenever a scope that ends in a .semicolon or a .comma is introduced.

Fields

underlying_writer: *Writer
disabled_offset: ?usize = null

Offset into the source at which formatting has been disabled with a zig fmt: off comment.

If non-null, the AutoIndentingStream will not write any bytes to the underlying writer. It will however continue to track the indentation level.

indent_count: usize = 0
indent_delta: usize
indent_stack: std.array_list.Managed(StackElem)
space_stack: std.array_list.Managed(SpaceElem)
space_mode: ?usize = null
disable_indent_committing: usize = 0
current_line_empty: bool = true
applied_indent: usize = 0

the most recently applied indent

Structs

Types

Functions

pub fn init(gpa: Allocator, w: *Writer, starting_indent_delta: usize) AutoIndentingStream
pub fn deinit(self: *AutoIndentingStream) void
pub fn writeAll(ais: *AutoIndentingStream, bytes: []const u8) Error!void
pub fn print(ais: *AutoIndentingStream, comptime format: []const u8, args: anytype) Error!void

Assumes that if the printed data ends with a newline, it is directly contained in the format string.

pub fn writeByte(ais: *AutoIndentingStream, byte: u8) Error!void
pub fn splatByteAll(ais: *AutoIndentingStream, byte: u8, n: usize) Error!void
pub fn setIndentDelta(ais: *AutoIndentingStream, new_indent_delta: usize) void
pub fn insertNewline(ais: *AutoIndentingStream) Error!void
pub fn maybeInsertNewline(ais: *AutoIndentingStream) Error!void

Insert a newline unless the current line is blank

pub fn isLineOverIndented(ais: *AutoIndentingStream) bool

Checks to see if the most recent indentation exceeds the currently pushed indents

fn resetLine(ais: *AutoIndentingStream) void
pub fn disableIndentCommitting(ais: *AutoIndentingStream) void

Disables indentation level changes during the next newlines until re-enabled.

pub fn enableIndentCommitting(ais: *AutoIndentingStream) void
pub fn pushSpace(ais: *AutoIndentingStream, space: Space) !void
pub fn popSpace(ais: *AutoIndentingStream) void
pub fn enableSpaceMode(ais: *AutoIndentingStream, space: Space) void

Sets current indentation level to be the same as that of the last pushSpace.

pub fn disableSpaceMode(ais: *AutoIndentingStream) void
pub fn lastSpaceModeIndent(ais: *AutoIndentingStream) usize
pub fn pushIndent(ais: *AutoIndentingStream, indent_type: IndentType) !void

Push default indentation Doesn't actually write any indentation. Just primes the stream to be able to write the correct indentation if it needs to.

pub fn forcePushIndent(ais: *AutoIndentingStream, indent_type: IndentType) !void

Forces an indentation level to be realized.

pub fn popIndent(ais: *AutoIndentingStream) void
pub fn forceLastIndent(ais: *AutoIndentingStream) void

Forces the last pushed indent to be realized

pub fn indentStackEmpty(ais: *AutoIndentingStream) bool
fn applyIndent(ais: *AutoIndentingStream) Error!void

Writes ' ' bytes if the current line is empty

fn currentIndent(ais: *AutoIndentingStream) usize

File

zig/Ast/Render.zig:3296
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
const AutoIndentingStream = struct {
    underlying_writer: *Writer,

    /// Offset into the source at which formatting has been disabled with
    /// a `zig fmt: off` comment.
    ///
    /// If non-null, the AutoIndentingStream will not write any bytes
    /// to the underlying writer. It will however continue to track the
    /// indentation level.
    disabled_offset: ?usize = null,

    indent_count: usize = 0,
    indent_delta: usize,
    indent_stack: std.array_list.Managed(StackElem),
    space_stack: std.array_list.Managed(SpaceElem),
    space_mode: ?usize = null,
    disable_indent_committing: usize = 0,
    current_line_empty: bool = true,
    /// the most recently applied indent
    applied_indent: usize = 0,

    pub const IndentType = enum {
        normal,
        after_equals,
        binop,
        field_access,
    };
    const StackElem = struct {
        indent_type: IndentType,
        realized: bool,
    };
    const SpaceElem = struct {
        space: Space,
        indent_count: usize,
    };

    pub fn init(gpa: Allocator, w: *Writer, starting_indent_delta: usize) AutoIndentingStream {
        return .{
            .underlying_writer = w,
            .indent_delta = starting_indent_delta,
            .indent_stack = .init(gpa),
            .space_stack = .init(gpa),
        };
    }

    pub fn deinit(self: *AutoIndentingStream) void {
        self.indent_stack.deinit();
        self.space_stack.deinit();
    }

    pub fn writeAll(ais: *AutoIndentingStream, bytes: []const u8) Error!void {
        if (bytes.len == 0) return;
        try ais.applyIndent();
        if (ais.disabled_offset == null) try ais.underlying_writer.writeAll(bytes);
        if (bytes[bytes.len - 1] == '\n') ais.resetLine();
    }

    /// Assumes that if the printed data ends with a newline, it is directly
    /// contained in the format string.
    pub fn print(ais: *AutoIndentingStream, comptime format: []const u8, args: anytype) Error!void {
        try ais.applyIndent();
        if (ais.disabled_offset == null) try ais.underlying_writer.print(format, args);
        if (format[format.len - 1] == '\n') ais.resetLine();
    }

    pub fn writeByte(ais: *AutoIndentingStream, byte: u8) Error!void {
        try ais.applyIndent();
        if (ais.disabled_offset == null) try ais.underlying_writer.writeByte(byte);
        assert(byte != '\n');
    }

    pub fn splatByteAll(ais: *AutoIndentingStream, byte: u8, n: usize) Error!void {
        assert(byte != '\n');
        try ais.applyIndent();
        if (ais.disabled_offset == null) try ais.underlying_writer.splatByteAll(byte, n);
    }

    // Change the indent delta without changing the final indentation level
    pub fn setIndentDelta(ais: *AutoIndentingStream, new_indent_delta: usize) void {
        if (ais.indent_delta == new_indent_delta) {
            return;
        } else if (ais.indent_delta > new_indent_delta) {
            assert(ais.indent_delta % new_indent_delta == 0);
            ais.indent_count = ais.indent_count * (ais.indent_delta / new_indent_delta);
        } else {
            // assert that the current indentation (in spaces) in a multiple of the new delta
            assert((ais.indent_count * ais.indent_delta) % new_indent_delta == 0);
            ais.indent_count = ais.indent_count / (new_indent_delta / ais.indent_delta);
        }
        ais.indent_delta = new_indent_delta;
    }

    pub fn insertNewline(ais: *AutoIndentingStream) Error!void {
        if (ais.disabled_offset == null) try ais.underlying_writer.writeByte('\n');
        ais.resetLine();
    }

    /// Insert a newline unless the current line is blank
    pub fn maybeInsertNewline(ais: *AutoIndentingStream) Error!void {
        if (!ais.current_line_empty)
            try ais.insertNewline();
    }

    /// Checks to see if the most recent indentation exceeds the currently pushed indents
    pub fn isLineOverIndented(ais: *AutoIndentingStream) bool {
        if (ais.current_line_empty) return false;
        return ais.applied_indent > ais.currentIndent();
    }

    fn resetLine(ais: *AutoIndentingStream) void {
        ais.current_line_empty = true;

        if (ais.disable_indent_committing > 0) return;

        if (ais.indent_stack.items.len > 0) {
            // By default, we realize the most recent indentation scope.
            var to_realize = ais.indent_stack.items.len - 1;

            if (ais.indent_stack.items.len >= 2 and
                ais.indent_stack.items[to_realize - 1].indent_type == .after_equals and
                ais.indent_stack.items[to_realize - 1].realized and
                ais.indent_stack.items[to_realize].indent_type == .binop)
            {
                // If we are in a .binop scope and our direct parent is .after_equals, don't indent.
                // This ensures correct indentation in the below example:
                //
                //        const foo =
                //            (x >= 'a' and x <= 'z') or         //<-- we are here
                //            (x >= 'A' and x <= 'Z');
                //
                return;
            }

            if (ais.indent_stack.items[to_realize].indent_type == .field_access) {
                // Only realize the top-most field_access in a chain.
                while (to_realize > 0 and ais.indent_stack.items[to_realize - 1].indent_type == .field_access)
                    to_realize -= 1;
            }

            if (ais.indent_stack.items[to_realize].realized) return;
            ais.indent_stack.items[to_realize].realized = true;
            ais.indent_count += 1;
        }
    }

    /// Disables indentation level changes during the next newlines until re-enabled.
    pub fn disableIndentCommitting(ais: *AutoIndentingStream) void {
        ais.disable_indent_committing += 1;
    }

    pub fn enableIndentCommitting(ais: *AutoIndentingStream) void {
        assert(ais.disable_indent_committing > 0);
        ais.disable_indent_committing -= 1;
    }

    pub fn pushSpace(ais: *AutoIndentingStream, space: Space) !void {
        try ais.space_stack.append(.{ .space = space, .indent_count = ais.indent_count });
    }

    pub fn popSpace(ais: *AutoIndentingStream) void {
        _ = ais.space_stack.pop();
    }

    /// Sets current indentation level to be the same as that of the last pushSpace.
    pub fn enableSpaceMode(ais: *AutoIndentingStream, space: Space) void {
        if (ais.space_stack.items.len == 0) return;
        const curr = ais.space_stack.last().?;
        if (curr.space != space) return;
        ais.space_mode = curr.indent_count;
    }

    pub fn disableSpaceMode(ais: *AutoIndentingStream) void {
        ais.space_mode = null;
    }

    pub fn lastSpaceModeIndent(ais: *AutoIndentingStream) usize {
        if (ais.space_stack.items.len == 0) return 0;
        return ais.space_stack.last().?.indent_count * ais.indent_delta;
    }

    /// Push default indentation
    /// Doesn't actually write any indentation.
    /// Just primes the stream to be able to write the correct indentation if it needs to.
    pub fn pushIndent(ais: *AutoIndentingStream, indent_type: IndentType) !void {
        try ais.indent_stack.append(.{ .indent_type = indent_type, .realized = false });
    }

    /// Forces an indentation level to be realized.
    pub fn forcePushIndent(ais: *AutoIndentingStream, indent_type: IndentType) !void {
        try ais.indent_stack.append(.{ .indent_type = indent_type, .realized = true });
        ais.indent_count += 1;
    }

    pub fn popIndent(ais: *AutoIndentingStream) void {
        if (ais.indent_stack.pop().?.realized) {
            ais.indent_count -= 1;
        }
    }

    /// Forces the last pushed indent to be realized
    pub fn forceLastIndent(ais: *AutoIndentingStream) void {
        const top = &ais.indent_stack.items[ais.indent_stack.items.len - 1];
        if (!top.realized) {
            top.realized = true;
            ais.indent_count += 1;
        }
    }

    pub fn indentStackEmpty(ais: *AutoIndentingStream) bool {
        return ais.indent_stack.items.len == 0;
    }

    /// Writes ' ' bytes if the current line is empty
    fn applyIndent(ais: *AutoIndentingStream) Error!void {
        const current_indent = ais.currentIndent();
        if (ais.current_line_empty) {
            if (current_indent > 0 and ais.disabled_offset == null) {
                try ais.underlying_writer.splatByteAll(' ', current_indent);
            }
            ais.applied_indent = current_indent;
        }
        ais.current_line_empty = false;
    }

    fn currentIndent(ais: *AutoIndentingStream) usize {
        const indent_count = ais.space_mode orelse ais.indent_count;
        return indent_count * ais.indent_delta;
    }
}