zig/lib/std/io/auto_indenting_stream.zig
2020-10-26 17:41:29 +01:00

155 lines
5.6 KiB
Zig

// SPDX-License-Identifier: MIT
// Copyright (c) 2015-2020 Zig Contributors
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
// The MIT license requires this copyright notice to be included in all copies
// and substantial portions of the software.
const std = @import("../std.zig");
const io = std.io;
const mem = std.mem;
const assert = std.debug.assert;
/// Automatically inserts indentation of written data by keeping
/// track of the current indentation level
pub fn AutoIndentingStream(comptime UnderlyingWriter: type) type {
return struct {
const Self = @This();
pub const Error = UnderlyingWriter.Error;
pub const Writer = io.Writer(*Self, Error, write);
underlying_writer: UnderlyingWriter,
indent_count: usize = 0,
indent_delta: usize,
current_line_empty: bool = true,
indent_one_shot_count: usize = 0, // automatically popped when applied
applied_indent: usize = 0, // the most recently applied indent
indent_next_line: usize = 0, // not used until the next line
pub fn writer(self: *Self) Writer {
return .{ .context = self };
}
pub fn write(self: *Self, bytes: []const u8) Error!usize {
if (bytes.len == 0)
return @as(usize, 0);
try self.applyIndent();
return self.writeNoIndent(bytes);
}
// Change the indent delta without changing the final indentation level
pub fn setIndentDelta(self: *Self, indent_delta: usize) void {
if (self.indent_delta == indent_delta) {
return;
} else if (self.indent_delta > indent_delta) {
assert(self.indent_delta % indent_delta == 0);
self.indent_count = self.indent_count * (self.indent_delta / indent_delta);
} else {
// assert that the current indentation (in spaces) in a multiple of the new delta
assert((self.indent_count * self.indent_delta) % indent_delta == 0);
self.indent_count = self.indent_count / (indent_delta / self.indent_delta);
}
self.indent_delta = indent_delta;
}
fn writeNoIndent(self: *Self, bytes: []const u8) Error!usize {
if (bytes.len == 0)
return @as(usize, 0);
try self.underlying_writer.writeAll(bytes);
if (bytes[bytes.len - 1] == '\n')
self.resetLine();
return bytes.len;
}
pub fn insertNewline(self: *Self) Error!void {
_ = try self.writeNoIndent("\n");
}
fn resetLine(self: *Self) void {
self.current_line_empty = true;
self.indent_next_line = 0;
}
/// Insert a newline unless the current line is blank
pub fn maybeInsertNewline(self: *Self) Error!void {
if (!self.current_line_empty)
try self.insertNewline();
}
/// Push default indentation
pub fn pushIndent(self: *Self) void {
// Doesn't actually write any indentation.
// Just primes the stream to be able to write the correct indentation if it needs to.
self.indent_count += 1;
}
/// Push an indent that is automatically popped after being applied
pub fn pushIndentOneShot(self: *Self) void {
self.indent_one_shot_count += 1;
self.pushIndent();
}
/// Turns all one-shot indents into regular indents
/// Returns number of indents that must now be manually popped
pub fn lockOneShotIndent(self: *Self) usize {
var locked_count = self.indent_one_shot_count;
self.indent_one_shot_count = 0;
return locked_count;
}
/// Push an indent that should not take effect until the next line
pub fn pushIndentNextLine(self: *Self) void {
self.indent_next_line += 1;
self.pushIndent();
}
pub fn popIndent(self: *Self) void {
assert(self.indent_count != 0);
self.indent_count -= 1;
if (self.indent_next_line > 0)
self.indent_next_line -= 1;
}
/// Writes ' ' bytes if the current line is empty
fn applyIndent(self: *Self) Error!void {
const current_indent = self.currentIndent();
if (self.current_line_empty and current_indent > 0) {
try self.underlying_writer.writeByteNTimes(' ', current_indent);
self.applied_indent = current_indent;
}
self.indent_count -= self.indent_one_shot_count;
self.indent_one_shot_count = 0;
self.current_line_empty = false;
}
/// Checks to see if the most recent indentation exceeds the currently pushed indents
pub fn isLineOverIndented(self: *Self) bool {
if (self.current_line_empty) return false;
return self.applied_indent > self.currentIndent();
}
fn currentIndent(self: *Self) usize {
var indent_current: usize = 0;
if (self.indent_count > 0) {
const indent_count = self.indent_count - self.indent_next_line;
indent_current = indent_count * self.indent_delta;
}
return indent_current;
}
};
}
pub fn autoIndentingStream(
indent_delta: usize,
underlying_writer: anytype,
) AutoIndentingStream(@TypeOf(underlying_writer)) {
return AutoIndentingStream(@TypeOf(underlying_writer)){
.underlying_writer = underlying_writer,
.indent_delta = indent_delta,
};
}