const es = @import("root"); const std = @import("std"); const Allocator = std.mem.Allocator; const ArrayList = std.ArrayList; const Buffer = es.Buffer; const Config = es.Config; const Highlight = es.Highlight; const Row = @This(); const StringBuilder = es.StringBuilder; allocator: Allocator, idx: usize, data: ArrayList(u8), rdata: ArrayList(u8), hldata: ArrayList(Highlight), ends_with_open_comment: bool, pub fn init(allocator: Allocator, idx: usize, data: []const u8) !Row { var self = Row{ .allocator = allocator, .idx = idx, .data = ArrayList(u8).init(allocator), .rdata = ArrayList(u8).init(allocator), .hldata = ArrayList(Highlight).init(allocator), .ends_with_open_comment = false, }; errdefer self.deinit(); try self.data.appendSlice(data); return self; } pub fn deinit(self: Row) void { self.data.deinit(); self.rdata.deinit(); self.hldata.deinit(); } pub fn appendString(self: *Row, buf: *Buffer, str: []const u8) !void { try self.data.appendSlice(str); return self.update(buf); } pub fn cleanWhiteSpace(self: *Row, buf: *Buffer) !void { const orig_len = self.data.items.len; while (self.data.items.len > 0) { if (std.ascii.isWhitespace(self.data.items[self.data.items.len - 1])) { _ = self.data.pop(); } else { break; } } if (orig_len != self.data.items.len) { buf.edited = true; try self.update(buf); } } pub fn cxToRx(self: Row, config: Config, cx: usize) usize { var rx: usize = 0; var i: usize = 0; while (i < cx) : (i += 1) { if (self.data.items[i] == '\t') { rx += config.tab_stop - (rx % config.tab_stop); } else { rx += 1; } } return rx; } pub fn deleteChar(self: *Row, buf: *Buffer, at: usize) !void { _ = self.data.orderedRemove(at); try self.update(buf); } pub fn indentationSize(self: Row) usize { var idx: usize = 0; while (idx < self.data.items.len) : (idx += 1) { if (!std.ascii.isWhitespace(self.data.items[idx])) { break; } } return idx; } pub fn insertChar(self: *Row, buf: *Buffer, at: usize, char: u8) !void { try self.data.insert(at, char); try self.update(buf); } pub fn rxToCx(self: Row, config: Config, rx: usize) usize { if (rx == 0) { return 0; } var cur_rx: usize = 0; for (self.data.items, 0..) |char, cx| { if (char == '\t') { cur_rx += config.tab_stop - (cur_rx % config.tab_stop); } else { cur_rx += 1; } if (cur_rx >= rx) { return cx + 1; } } return self.data.items.len; } // TODO: I don't like that this is modifying both row and buffer (parent of row) pub fn update(self: *Row, buf: *Buffer) !void { self.rdata.clearRetainingCapacity(); for (self.data.items) |char| { if (char == '\t') { const len = buf.config.tab_stop - self.rdata.items.len % buf.config.tab_stop; try self.rdata.appendNTimes(' ', len); } else { try self.rdata.append(char); } } try self.updateSyntax(buf); } const UpdateSyntaxError = std.mem.Allocator.Error; pub fn updateSyntax(self: *Row, buf: *Buffer) UpdateSyntaxError!void { try self.hldata.resize(self.rdata.items.len); @memset(self.hldata.items, .normal); if (buf.syntax == null) { return; } const syntax = buf.syntax.?; var in_comment = if (self.idx > 0) buf.rows.items[self.idx - 1].ends_with_open_comment else false; var curr_str_quote: ?u8 = null; var i: usize = 0; var last_sep: usize = 0; main_loop: while (i < self.rdata.items.len) { const prev_hl = if (i > 0) self.hldata.items[i - 1] else .normal; if (syntax.singleline_comment_start) |scs| { std.debug.assert(scs.len != 0); if (curr_str_quote == null and !in_comment and scs.len + i <= self.rdata.items.len) { if (std.mem.eql(u8, scs, self.rdata.items[i..(i + scs.len)])) { @memset(self.hldata.items[i..], .comment); break :main_loop; } } } if (syntax.multiline_comment_start) |mcs| { std.debug.assert(mcs.len != 0); if (syntax.multiline_comment_end) |mce| { std.debug.assert(mce.len != 0); if (curr_str_quote == null) { if (in_comment) { self.hldata.items[i] = .comment_ml; if (mce.len + i <= self.rdata.items.len and std.mem.eql(u8, mce, self.rdata.items[i..(i + mce.len)])) { @memset(self.hldata.items[i..(i + mce.len)], .comment_ml); i += mce.len; in_comment = false; last_sep = i; } else { i += 1; continue :main_loop; } } else if (mcs.len + i <= self.rdata.items.len and std.mem.eql(u8, mcs, self.rdata.items[i..(i + mcs.len)])) { @memset(self.hldata.items[i..(i + mcs.len)], .comment_ml); i += mcs.len; in_comment = true; continue :main_loop; } } } } const c = self.rdata.items[i]; if (syntax.flags.hl_strings) { if (curr_str_quote) |quote| { self.hldata.items[i] = .string; i += 1; // Pretty dumb way of detecting \" or \' but it works \shrug/ if (c == '\\' and i < self.rdata.items.len) { self.hldata.items[i] = .string; i += 1; } else if (c == quote) { curr_str_quote = null; last_sep = i; } continue :main_loop; } else { // TODO: Move this to syntax struct if (c == '"' or c == '\'') { curr_str_quote = c; self.hldata.items[i] = .string; i += 1; continue :main_loop; } } } if (syntax.flags.hl_numbers) { if ((std.ascii.isDigit(c) and (last_sep == i or prev_hl == .number)) or (c == '.' and prev_hl == .number)) { self.hldata.items[i] = .number; i += 1; continue :main_loop; } } if (syntax.isSeparator(c)) { const id = self.rdata.items[last_sep..i]; if (syntax.keyword_classifier(id)) |hl| { @memset(self.hldata.items[last_sep..i], hl); } last_sep = i + 1; } i += 1; } if (!in_comment) { const id = self.rdata.items[last_sep..self.rdata.items.len]; if (syntax.keyword_classifier(id)) |hl| { @memset(self.hldata.items[last_sep..self.rdata.items.len], hl); } } if (in_comment != self.ends_with_open_comment) { self.ends_with_open_comment = in_comment; if (self.idx + 1 < buf.rows.items.len) { try buf.rows.items[self.idx + 1].updateSyntax(buf); } } }