diff --git a/src/Project.zig b/src/Project.zig index 44fb516..d9fa959 100644 --- a/src/Project.zig +++ b/src/Project.zig @@ -302,7 +302,7 @@ pub fn did_open(self: *Self, file_path: []const u8, file_type: []const u8, langu }); } -pub fn did_change(self: *Self, file_path: []const u8, version: usize, root_dst_addr: usize, root_src_addr: usize) GetFileLspError!void { +pub fn did_change(self: *Self, file_path: []const u8, version: usize, root_dst_addr: usize, root_src_addr: usize, eol_mode: Buffer.EolMode) GetFileLspError!void { const lsp = try self.get_file_lsp(file_path); const uri = try self.make_URI(file_path); defer self.allocator.free(uri); @@ -325,8 +325,8 @@ pub fn did_change(self: *Self, file_path: []const u8, version: usize, root_dst_a dizzy_edits.deinit(self.allocator); } - try root_dst.store(dst.writer()); - try root_src.store(src.writer()); + try root_dst.store(dst.writer(), eol_mode); + try root_src.store(src.writer(), eol_mode); const scratch_len = 4 * (dst.items.len + src.items.len) + 2; try scratch.ensureTotalCapacity(self.allocator, scratch_len); diff --git a/src/buffer/Buffer.zig b/src/buffer/Buffer.zig index 9fd5635..4fa7023 100644 --- a/src/buffer/Buffer.zig +++ b/src/buffer/Buffer.zig @@ -32,11 +32,15 @@ file_buf: ?[]const u8 = null, file_path: []const u8 = "", last_save: ?Root = null, file_exists: bool = true, +file_eol_mode: EolMode = .lf, undo_history: ?*UndoNode = null, redo_history: ?*UndoNode = null, curr_history: ?*UndoNode = null, +pub const EolMode = enum { lf, crlf }; +pub const EolModeTag = @typeInfo(EolMode).Enum.tag_type; + const UndoNode = struct { root: Root, next: ?*UndoNode = null, @@ -850,16 +854,18 @@ const Node = union(enum) { return .{ line, col, self }; } - pub fn store(self: *const Node, writer: anytype) !void { + pub fn store(self: *const Node, writer: anytype, eol_mode: EolMode) !void { switch (self.*) { .node => |*node| { - try node.left.store(writer); - try node.right.store(writer); + try node.left.store(writer, eol_mode); + try node.right.store(writer, eol_mode); }, .leaf => |*leaf| { _ = try writer.write(leaf.buf); - if (leaf.eol) - _ = try writer.write("\n"); + if (leaf.eol) switch (eol_mode) { + .lf => _ = try writer.write("\n"), + .crlf => _ = try writer.write("\r\n"), + }; }, } } @@ -934,10 +940,10 @@ const Node = union(enum) { .buf = try allocator.alloc(u8, pattern.len * 2), }; defer allocator.free(ctx.buf); - return self.store(ctx.writer()); + return self.store(ctx.writer(), .lf); } - pub fn get_byte_pos(self: *const Node, pos_: Cursor, metrics_: Metrics) !usize { + pub fn get_byte_pos(self: *const Node, pos_: Cursor, metrics_: Metrics, eol_mode: EolMode) !usize { const Ctx = struct { line: usize = 0, abs_col: usize = 0, @@ -980,7 +986,7 @@ const Node = union(enum) { .pos = pos_, .metrics = metrics_, }; - self.store(ctx.writer()) catch |e| switch (e) { + self.store(ctx.writer(), eol_mode) catch |e| switch (e) { error.Stop => return ctx.byte_pos, }; return error.NotFound; @@ -1035,8 +1041,9 @@ fn new_file(self: *const Self, file_exists: *bool) !Root { return Leaf.new(self.allocator, "", true, false); } -pub fn load(self: *const Self, reader: anytype, size: usize) !Root { - const eol = '\n'; +pub fn load(self: *const Self, reader: anytype, size: usize, eol_mode: *EolMode) !Root { + const lf = '\n'; + const cr = '\r'; var buf = try self.external_allocator.alloc(u8, size); const self_ = @constCast(self); self_.file_buf = buf; @@ -1047,9 +1054,14 @@ pub fn load(self: *const Self, reader: anytype, size: usize) !Root { if (final_read != 0) @panic("unexpected data in final read"); + eol_mode.* = .lf; var leaf_count: usize = 1; for (0..buf.len) |i| { - if (buf[i] == eol) leaf_count += 1; + if (buf[i] == lf) { + leaf_count += 1; + if (i > 0 and buf[i - 1] == cr) + eol_mode.* = .crlf; + } } var leaves = try self.external_allocator.alloc(Node, leaf_count); @@ -1057,8 +1069,9 @@ pub fn load(self: *const Self, reader: anytype, size: usize) !Root { var cur_leaf: usize = 0; var b: usize = 0; for (0..buf.len) |i| { - if (buf[i] == eol) { - const line = buf[b..i]; + if (buf[i] == lf) { + const line_end = if (i > 0 and buf[i - 1] == cr) i - 1 else i; + const line = buf[b..line_end]; leaves[cur_leaf] = .{ .leaf = .{ .buf = line, .bol = true, .eol = true } }; cur_leaf += 1; b = i + 1; @@ -1071,19 +1084,19 @@ pub fn load(self: *const Self, reader: anytype, size: usize) !Root { return Node.merge_in_place(leaves, self.allocator); } -pub fn load_from_string(self: *const Self, s: []const u8) !Root { +pub fn load_from_string(self: *const Self, s: []const u8, eol_mode: *EolMode) !Root { var stream = std.io.fixedBufferStream(s); - return self.load(stream.reader(), s.len); + return self.load(stream.reader(), s.len, eol_mode); } pub fn load_from_string_and_update(self: *Self, file_path: []const u8, s: []const u8) !void { - self.root = try self.load_from_string(s); + self.root = try self.load_from_string(s, &self.file_eol_mode); self.file_path = try self.allocator.dupe(u8, file_path); self.last_save = self.root; self.file_exists = false; } -pub fn load_from_file(self: *const Self, file_path: []const u8, file_exists: *bool) !Root { +pub fn load_from_file(self: *const Self, file_path: []const u8, file_exists: *bool, eol_mode: *EolMode) !Root { const file = cwd().openFile(file_path, .{ .mode = .read_only }) catch |e| switch (e) { error.FileNotFound => return self.new_file(file_exists), else => return e, @@ -1092,15 +1105,17 @@ pub fn load_from_file(self: *const Self, file_path: []const u8, file_exists: *bo file_exists.* = true; defer file.close(); const stat = try file.stat(); - return self.load(file.reader(), @intCast(stat.size)); + return self.load(file.reader(), @intCast(stat.size), eol_mode); } pub fn load_from_file_and_update(self: *Self, file_path: []const u8) !void { var file_exists: bool = false; - self.root = try self.load_from_file(file_path, &file_exists); + var eol_mode: EolMode = .lf; + self.root = try self.load_from_file(file_path, &file_exists, &eol_mode); self.file_path = try self.allocator.dupe(u8, file_path); self.last_save = self.root; self.file_exists = file_exists; + self.file_eol_mode = eol_mode; } pub fn store_to_string(self: *const Self, allocator: Allocator) ![]u8 { @@ -1117,7 +1132,7 @@ fn store_to_file_const(self: *const Self, file: anytype) !void { const file_writer: std.fs.File.Writer = file.writer(); var buffered_writer: BufferedWriter = .{ .unbuffered_writer = file_writer }; - try self.root.store(Writer{ .context = &buffered_writer }); + try self.root.store(Writer{ .context = &buffered_writer }, self.file_eol_mode); try buffered_writer.flush(); } diff --git a/src/diff.zig b/src/diff.zig index 759b7e7..1867504 100644 --- a/src/diff.zig +++ b/src/diff.zig @@ -63,14 +63,15 @@ const Process = struct { var cb: usize = 0; var root_dst: usize = 0; var root_src: usize = 0; + var eol_mode: Buffer.EolModeTag = @intFromEnum(Buffer.EolMode.lf); - return if (try m.match(.{ "D", tp.extract(&cb), tp.extract(&root_dst), tp.extract(&root_src) })) - self.diff(from, cb, root_dst, root_src) catch |e| tp.exit_error(e, @errorReturnTrace()) + return if (try m.match(.{ "D", tp.extract(&cb), tp.extract(&root_dst), tp.extract(&root_src), tp.extract(&eol_mode) })) + self.diff(from, cb, root_dst, root_src, @enumFromInt(eol_mode)) catch |e| tp.exit_error(e, @errorReturnTrace()) else if (try m.match(.{"shutdown"})) tp.exit_normal(); } - fn diff(self: *Process, from: tp.pid_ref, cb_addr: usize, root_new_addr: usize, root_old_addr: usize) !void { + fn diff(self: *Process, from: tp.pid_ref, cb_addr: usize, root_new_addr: usize, root_old_addr: usize, eol_mode: Buffer.EolMode) !void { const frame = tracy.initZone(@src(), .{ .name = "diff" }); defer frame.deinit(); const cb: *CallBack = if (cb_addr == 0) return else @ptrFromInt(cb_addr); @@ -90,8 +91,8 @@ const Process = struct { dizzy_edits.deinit(self.allocator); } - try root_dst.store(dst.writer()); - try root_src.store(src.writer()); + try root_dst.store(dst.writer(), eol_mode); + try root_src.store(src.writer(), eol_mode); const scratch_len = 4 * (dst.items.len + src.items.len) + 2; try scratch.ensureTotalCapacity(self.allocator, scratch_len); @@ -158,6 +159,6 @@ const Process = struct { pub const CallBack = fn (from: tp.pid_ref, edits: []Edit) void; -pub fn diff(self: Self, cb: *const CallBack, root_dst: Buffer.Root, root_src: Buffer.Root) tp.result { - if (self.pid) |pid| try pid.send(.{ "D", @intFromPtr(cb), @intFromPtr(root_dst), @intFromPtr(root_src) }); +pub fn diff(self: Self, cb: *const CallBack, root_dst: Buffer.Root, root_src: Buffer.Root, eol_mode: Buffer.EolMode) tp.result { + if (self.pid) |pid| try pid.send(.{ "D", @intFromPtr(cb), @intFromPtr(root_dst), @intFromPtr(root_src), @intFromEnum(eol_mode) }); } diff --git a/src/project_manager.zig b/src/project_manager.zig index aa15d9d..40a6da5 100644 --- a/src/project_manager.zig +++ b/src/project_manager.zig @@ -5,6 +5,7 @@ const log = @import("log"); const tracy = @import("tracy"); const FileType = @import("syntax").FileType; const root = @import("root"); +const Buffer = @import("Buffer"); const builtin = @import("builtin"); const Project = @import("Project.zig"); @@ -103,11 +104,11 @@ pub fn did_open(file_path: []const u8, file_type: *const FileType, version: usiz return send(.{ "did_open", project, file_path, file_type.name, file_type.language_server, version, text_ptr, text.len }); } -pub fn did_change(file_path: []const u8, version: usize, root_dst: usize, root_src: usize) (ProjectError || SendError)!void { +pub fn did_change(file_path: []const u8, version: usize, root_dst: usize, root_src: usize, eol_mode: Buffer.EolMode) (ProjectError || SendError)!void { const project = tp.env.get().str("project"); if (project.len == 0) return error.NoProject; - return send(.{ "did_change", project, file_path, version, root_dst, root_src }); + return send(.{ "did_change", project, file_path, version, root_dst, root_src, @intFromEnum(eol_mode) }); } pub fn did_save(file_path: []const u8) (ProjectError || SendError)!void { @@ -268,6 +269,7 @@ const Process = struct { var root_dst: usize = 0; var root_src: usize = 0; + var eol_mode: Buffer.EolModeTag = @intFromEnum(Buffer.EolMode.lf); if (try cbor.match(m.buf, .{ "walk_tree_entry", tp.extract(&project_directory), tp.extract(&path), tp.extract(&high), tp.extract(&low) })) { const mtime = (@as(i128, @intCast(high)) << 64) | @as(i128, @intCast(low)); @@ -303,8 +305,8 @@ const Process = struct { } else if (try cbor.match(m.buf, .{ "did_open", tp.extract(&project_directory), tp.extract(&path), tp.extract(&file_type), tp.extract_cbor(&language_server), tp.extract(&version), tp.extract(&text_ptr), tp.extract(&text_len) })) { const text = if (text_len > 0) @as([*]const u8, @ptrFromInt(text_ptr))[0..text_len] else ""; self.did_open(project_directory, path, file_type, language_server, version, text) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.SendFailed; - } else if (try cbor.match(m.buf, .{ "did_change", tp.extract(&project_directory), tp.extract(&path), tp.extract(&version), tp.extract(&root_dst), tp.extract(&root_src) })) { - self.did_change(project_directory, path, version, root_dst, root_src) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.SendFailed; + } else if (try cbor.match(m.buf, .{ "did_change", tp.extract(&project_directory), tp.extract(&path), tp.extract(&version), tp.extract(&root_dst), tp.extract(&root_src), tp.extract(&eol_mode) })) { + self.did_change(project_directory, path, version, root_dst, root_src, @enumFromInt(eol_mode)) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.SendFailed; } else if (try cbor.match(m.buf, .{ "did_save", tp.extract(&project_directory), tp.extract(&path) })) { self.did_save(project_directory, path) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.SendFailed; } else if (try cbor.match(m.buf, .{ "did_close", tp.extract(&project_directory), tp.extract(&path) })) { @@ -409,11 +411,11 @@ const Process = struct { return project.did_open(file_path, file_type, language_server, version, text); } - fn did_change(self: *Process, project_directory: []const u8, file_path: []const u8, version: usize, root_dst: usize, root_src: usize) (ProjectError || Project.GetFileLspError)!void { + fn did_change(self: *Process, project_directory: []const u8, file_path: []const u8, version: usize, root_dst: usize, root_src: usize, eol_mode: Buffer.EolMode) (ProjectError || Project.GetFileLspError)!void { const frame = tracy.initZone(@src(), .{ .name = module_name ++ ".did_change" }); defer frame.deinit(); const project = self.projects.get(project_directory) orelse return error.NoProject; - return project.did_change(file_path, version, root_dst, root_src); + return project.did_change(file_path, version, root_dst, root_src, eol_mode); } fn did_save(self: *Process, project_directory: []const u8, file_path: []const u8) (ProjectError || Project.GetFileLspError)!void { diff --git a/src/tui/editor.zig b/src/tui/editor.zig index a974652..c8916af 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -206,6 +206,7 @@ pub const Editor = struct { whole_file: ?std.ArrayList(u8), bytes: usize = 0, chunks: usize = 0, + eol_mode: Buffer.EolMode = .lf, } = null, matches: Match.List, match_token: usize = 0, @@ -238,6 +239,7 @@ pub const Editor = struct { matches: usize = 0, cursels: usize = 0, dirty: bool = false, + eol_mode: Buffer.EolMode = .lf, } = .{}, syntax: ?*syntax = null, @@ -357,6 +359,10 @@ pub const Editor = struct { return if (self.buffer) |p| p.root else error.Stop; } + fn buf_eol_mode(self: *const Self) !Buffer.EolMode { + return if (self.buffer) |p| p.file_eol_mode else error.Stop; + } + fn buf_a(self: *const Self) !Allocator { return if (self.buffer) |p| p.allocator else error.Stop; } @@ -406,7 +412,7 @@ pub const Editor = struct { const lang_override = tp.env.get().str("language"); var content = std.ArrayList(u8).init(self.allocator); defer content.deinit(); - try new_buf.root.store(content.writer()); + try new_buf.root.store(content.writer(), new_buf.file_eol_mode); const syn = if (lang_override.len > 0) syntax.create_file_type(self.allocator, content.items, lang_override) catch null else @@ -508,6 +514,11 @@ pub const Editor = struct { } fn update_buf(self: *Self, root: Buffer.Root) !void { + const b = self.buffer orelse return error.Stop; + return self.update_buf_and_eol_mode(root, b.file_eol_mode); + } + + fn update_buf_and_eol_mode(self: *Self, root: Buffer.Root, eol_mode: Buffer.EolMode) !void { const b = self.buffer orelse return error.Stop; var sfa = std.heap.stackFallback(512, self.allocator); const allocator = sfa.get(); @@ -515,6 +526,7 @@ pub const Editor = struct { defer allocator.free(meta); try b.store_undo(meta); b.update(root); + b.file_eol_mode = eol_mode; try self.send_editor_modified(); } @@ -1114,11 +1126,15 @@ pub const Editor = struct { const dirty = if (self.buffer) |buf| buf.is_dirty() else false; const root: ?Buffer.Root = self.buf_root() catch null; + const eol_mode = self.buf_eol_mode() catch .lf; if (token_from(self.last.root) != token_from(root)) { - try self.send_editor_update(self.last.root, root); + try self.send_editor_update(self.last.root, root, eol_mode); self.lsp_version += 1; } + if (self.last.eol_mode != eol_mode) + try self.send_editor_eol_mode(eol_mode); + if (self.last.dirty != dirty) try self.send_editor_dirty(dirty); @@ -1227,10 +1243,14 @@ pub const Editor = struct { return if (p) |p_| @intFromPtr(p_) else 0; } - fn send_editor_update(self: *const Self, old_root: ?Buffer.Root, new_root: ?Buffer.Root) !void { - _ = try self.handlers.msg(.{ "E", "update", token_from(new_root), token_from(old_root) }); + fn send_editor_update(self: *const Self, old_root: ?Buffer.Root, new_root: ?Buffer.Root, eol_mode: Buffer.EolMode) !void { + _ = try self.handlers.msg(.{ "E", "update", token_from(new_root), token_from(old_root), @intFromEnum(eol_mode) }); if (self.syntax) |_| if (self.file_path) |file_path| if (old_root != null and new_root != null) - project_manager.did_change(file_path, self.lsp_version, token_from(new_root), token_from(old_root)) catch {}; + project_manager.did_change(file_path, self.lsp_version, token_from(new_root), token_from(old_root), eol_mode) catch {}; + } + + fn send_editor_eol_mode(self: *const Self, eol_mode: Buffer.EolMode) !void { + _ = try self.handlers.msg(.{ "E", "eol_mode", @intFromEnum(eol_mode) }); } fn clamp_abs(self: *Self, abs: bool) void { @@ -1448,7 +1468,8 @@ pub const Editor = struct { match.nudge_insert(nudge); if (self.syntax) |syn| { const root = self.buf_root() catch return; - const start_byte = root.get_byte_pos(nudge.begin, self.plane.metrics()) catch return; + const eol_mode = self.buf_eol_mode() catch return; + const start_byte = root.get_byte_pos(nudge.begin, self.plane.metrics(), eol_mode) catch return; syn.edit(.{ .start_byte = @intCast(start_byte), .old_end_byte = @intCast(start_byte), @@ -1472,7 +1493,8 @@ pub const Editor = struct { }; if (self.syntax) |syn| { const root = self.buf_root() catch return; - const start_byte = root.get_byte_pos(nudge.begin, self.plane.metrics()) catch return; + const eol_mode = self.buf_eol_mode() catch return; + const start_byte = root.get_byte_pos(nudge.begin, self.plane.metrics(), eol_mode) catch return; syn.edit(.{ .start_byte = @intCast(start_byte), .old_end_byte = @intCast(start_byte + size), @@ -3002,6 +3024,7 @@ pub const Editor = struct { const frame = tracy.initZone(@src(), .{ .name = "editor update syntax" }); defer frame.deinit(); const root = try self.buf_root(); + const eol_mode = try self.buf_eol_mode(); const token = @intFromPtr(root); if (root.lines() > root_mod.max_syntax_lines) return; @@ -3011,7 +3034,7 @@ pub const Editor = struct { if (self.syntax_refresh_full) { var content = std.ArrayList(u8).init(self.allocator); defer content.deinit(); - try root.store(content.writer()); + try root.store(content.writer(), eol_mode); try syn.refresh_full(content.items); self.syntax_refresh_full = false; } else { @@ -3021,7 +3044,7 @@ pub const Editor = struct { } else { var content = std.ArrayList(u8).init(self.allocator); defer content.deinit(); - try root.store(content.writer()); + try root.store(content.writer(), eol_mode); self.syntax = if (tp.env.get().is("no-syntax")) null else @@ -3759,7 +3782,7 @@ pub const Editor = struct { self.cancel_all_selections(); self.cancel_all_matches(); if (state.whole_file) |buf| { - state.work_root = try b.load_from_string(buf.items); + state.work_root = try b.load_from_string(buf.items, &state.eol_mode); state.bytes = buf.items.len; state.chunks = 1; primary.cursor = state.old_primary.cursor; @@ -3770,7 +3793,7 @@ pub const Editor = struct { if (state.old_primary_reversed) sel.reverse(); primary.cursor = sel.end; } - try self.update_buf(state.work_root); + try self.update_buf_and_eol_mode(state.work_root, state.eol_mode); primary.cursor.clamp_to_buffer(state.work_root, self.plane.metrics()); self.logger.print("filter: done (bytes:{d} chunks:{d})", .{ state.bytes, state.chunks }); self.reset_syntax(); @@ -3844,6 +3867,14 @@ pub const Editor = struct { self.clamp(); } pub const to_lower_meta = .{ .description = "Convert selection or word to lower case" }; + + pub fn toggle_eol_mode(self: *Self, _: Context) Result { + if (self.buffer) |b| b.file_eol_mode = switch (b.file_eol_mode) { + .lf => .crlf, + .crlf => .lf, + }; + } + pub const toggle_eol_mode_meta = .{ .description = "Toggle end of line sequence" }; }; pub fn create(allocator: Allocator, parent: Widget) !Widget { diff --git a/src/tui/editor_gutter.zig b/src/tui/editor_gutter.zig index 3eb9fc4..6cb6462 100644 --- a/src/tui/editor_gutter.zig +++ b/src/tui/editor_gutter.zig @@ -306,7 +306,8 @@ fn diff_update(self: *Self) !void { const editor = self.editor; const new = editor.get_current_root() orelse return; const old = if (editor.buffer) |buffer| buffer.last_save orelse return else return; - return self.diff.diff(diff_result, new, old); + const eol_mode = if (editor.buffer) |buffer| buffer.file_eol_mode else return; + return self.diff.diff(diff_result, new, old, eol_mode); } fn diff_result(from: tp.pid_ref, edits: []diff.Edit) void { diff --git a/src/tui/status/filestate.zig b/src/tui/status/filestate.zig index 585b9aa..aa1afd6 100644 --- a/src/tui/status/filestate.zig +++ b/src/tui/status/filestate.zig @@ -2,6 +2,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const tp = @import("thespian"); const tracy = @import("tracy"); +const Buffer = @import("Buffer"); const root = @import("root"); const Plane = @import("renderer").Plane; @@ -29,6 +30,7 @@ file_exists: bool, file_dirty: bool = false, detailed: bool = false, file: bool = false, +eol_mode: Buffer.EolMode = .lf, const project_icon = ""; const Self = @This(); @@ -134,11 +136,16 @@ fn render_detailed(self: *Self, plane: *Plane, theme: *const Widget.Theme) void const project_name = tp.env.get().str("project"); _ = plane.print("{s} ({s})", .{ self.name, project_name }) catch {}; } else { + const eol_mode = switch (self.eol_mode) { + .lf => " [↩ = ␊]", + .crlf => " [↩ = ␍␊]", + }; + _ = plane.putstr(if (!self.file_exists) "󰽂" else if (self.file_dirty) "󰆓" else "󱣪") catch {}; _ = plane.print(" {s}:{d}:{d}", .{ self.name, self.line + 1, self.column + 1 }) catch {}; _ = plane.print(" of {d} lines", .{self.lines}) catch {}; if (self.file_type.len > 0) - _ = plane.print(" ({s})", .{self.file_type}) catch {}; + _ = plane.print(" ({s}){s}", .{ self.file_type, eol_mode }) catch {}; } return; } @@ -169,10 +176,13 @@ pub fn receive(self: *Self, _: *Button.State(Self), _: tp.pid_ref, m: tp.message var file_type: []const u8 = undefined; var file_icon: []const u8 = undefined; var file_dirty: bool = undefined; + var eol_mode: Buffer.EolModeTag = @intFromEnum(Buffer.EolMode.lf); if (try m.match(.{ "E", "pos", tp.extract(&self.lines), tp.extract(&self.line), tp.extract(&self.column) })) return false; if (try m.match(.{ "E", "dirty", tp.extract(&file_dirty) })) { self.file_dirty = file_dirty; + } else if (try m.match(.{ "E", "eol_mode", tp.extract(&eol_mode) })) { + self.eol_mode = @enumFromInt(eol_mode); } else if (try m.match(.{ "E", "save", tp.extract(&file_path) })) { @memcpy(self.name_buf[0..file_path.len], file_path); self.name = self.name_buf[0..file_path.len]; diff --git a/src/tui/status/linenumstate.zig b/src/tui/status/linenumstate.zig index bed26b6..3646f57 100644 --- a/src/tui/status/linenumstate.zig +++ b/src/tui/status/linenumstate.zig @@ -1,6 +1,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const tp = @import("thespian"); +const Buffer = @import("Buffer"); const Plane = @import("renderer").Plane; @@ -13,6 +14,7 @@ lines: usize = 0, column: usize = 0, buf: [256]u8 = undefined, rendered: [:0]const u8 = "", +eol_mode: Buffer.EolMode = .lf, const Self = @This(); @@ -47,14 +49,21 @@ pub fn render(self: *Self, btn: *Button.State(Self), theme: *const Widget.Theme) fn format(self: *Self) void { var fbs = std.io.fixedBufferStream(&self.buf); const writer = fbs.writer(); - std.fmt.format(writer, " Ln {d}, Col {d} ", .{ self.line + 1, self.column + 1 }) catch {}; + const eol_mode = switch (self.eol_mode) { + .lf => "", + .crlf => " [␍␊]", + }; + std.fmt.format(writer, "{s} Ln {d}, Col {d} ", .{ eol_mode, self.line + 1, self.column + 1 }) catch {}; self.rendered = @ptrCast(fbs.getWritten()); self.buf[self.rendered.len] = 0; } pub fn receive(self: *Self, _: *Button.State(Self), _: tp.pid_ref, m: tp.message) error{Exit}!bool { + var eol_mode: Buffer.EolModeTag = @intFromEnum(Buffer.EolMode.lf); if (try m.match(.{ "E", "pos", tp.extract(&self.lines), tp.extract(&self.line), tp.extract(&self.column) })) { self.format(); + } else if (try m.match(.{ "E", "eol_mode", tp.extract(&eol_mode) })) { + self.eol_mode = @enumFromInt(eol_mode); } else if (try m.match(.{ "E", "close" })) { self.lines = 0; self.line = 0;