155 lines
5.6 KiB
Zig
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,
|
|
};
|
|
}
|