TUI editor and editor backend written in Zig
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

add some language feature basics, including zig format-on-save

reykjalin.org 654230fa ecb64db9

verified
+129 -16
+42 -14
src/libfn/Editor.zig
··· 8 8 const IndexPos = @import("indexpos.zig").IndexPos; 9 9 const Range = @import("Range.zig"); 10 10 const Selection = @import("Selection.zig"); 11 + const Language = @import("Language.zig"); 12 + const Zig = @import("languages/Zig.zig"); 11 13 12 14 const Allocator = std.mem.Allocator; 13 15 ··· 50 52 /// An array tracking all of the selections in the editor. **Modifying this will cause undefined 51 53 /// behavior**. Use the methods on the editor to manipulate the selections instead. 52 54 selections: std.ArrayListUnmanaged(Selection), 55 + /// Adds language support features when present. 56 + language: ?Language = null, 53 57 54 58 pub const Command = union(enum) { 55 59 /// Adds a new cursor at the given position. ··· 115 119 const file = try std.fs.cwd().createFile(filename, .{ .read = true, .truncate = false }); 116 120 defer file.close(); 117 121 118 - // 3. Get a reader to read the file. 122 + self.text.clearAndFree(allocator); 119 123 120 - var buffer: [1024]u8 = undefined; 121 - var file_reader = file.reader(&buffer); 122 - var reader = &file_reader.interface; 124 + if (try file.getEndPos() > 0) { 125 + // 3. Get a reader to read the file. 123 126 124 - // 4. Read the file and store in state. 127 + var buffer: [1024]u8 = undefined; 128 + var file_reader = file.reader(&buffer); 129 + var reader = &file_reader.interface; 125 130 126 - self.text.clearAndFree(allocator); 131 + // 4. Read the file and store in state. 127 132 128 - var writer: std.Io.Writer.Allocating = .fromArrayList(allocator, &self.text); 129 - defer writer.deinit(); 133 + var writer: std.Io.Writer.Allocating = .fromArrayList(allocator, &self.text); 134 + defer writer.deinit(); 130 135 131 - // Stream from file to the text array list. 132 - _ = try reader.stream(&writer.writer, .unlimited); 133 - try writer.writer.flush(); 134 - self.text = writer.toArrayList(); 136 + // Stream from file to the text array list. 137 + _ = try reader.stream(&writer.writer, .unlimited); 138 + try writer.writer.flush(); 139 + self.text = writer.toArrayList(); 140 + } 135 141 136 142 // 5. Only after the file has been successfully read do we update file name and other state. 137 143 ··· 145 151 // 7. Tokenize the new text. 146 152 147 153 try self.tokenize(allocator); 154 + 155 + // 8. Initialize a language, if we have an implementation for it. 156 + 157 + if (std.mem.eql(u8, std.fs.path.extension(self.filename.items), ".zig")) { 158 + self.language = Zig.init(); 159 + } else { 160 + self.language = null; 161 + } 148 162 } 149 163 150 164 /// Saves the text to the current location based on the `filename` field. 151 - pub fn saveFile(self: *Editor) !void { 152 - const file = try std.fs.cwd().createFile(self.filename.items, .{}); 165 + pub fn saveFile(self: *Editor, gpa: std.mem.Allocator) !void { 166 + if (self.language) |*l| format_text: { 167 + const formatted = l.formatter.format(gpa, self.text.items) catch break :format_text; 168 + defer gpa.free(formatted); 169 + 170 + self.text.clearRetainingCapacity(); 171 + try self.text.ensureTotalCapacity(gpa, formatted.len); 172 + self.text.appendSliceAssumeCapacity(formatted); 173 + 174 + try self.updateLines(gpa); 175 + try self.tokenize(gpa); 176 + } 177 + 178 + std.log.debug("saving file: {s}", .{self.filename.items}); 179 + 180 + const file = try std.fs.cwd().createFile(self.filename.items, .{ .read = true, .truncate = true }); 153 181 defer file.close(); 154 182 155 183 var buffer: [1024]u8 = undefined;
+15
src/libfn/Formatter.zig
··· 1 + const std = @import("std"); 2 + 3 + const Formatter = @This(); 4 + 5 + vtable: *const VTable, 6 + 7 + pub const VTable = struct { 8 + format: *const fn (f: *Formatter, gpa: std.mem.Allocator, input: []const u8) anyerror![]const u8 = format, 9 + }; 10 + 11 + /// Runs a formatter on the provided input, and returns the formatted output. Caller owns the 12 + /// returned memory. 13 + pub fn format(f: *Formatter, gpa: std.mem.Allocator, input: []const u8) anyerror![]const u8 { 14 + return try f.vtable.format(f, gpa, input); 15 + }
+7
src/libfn/Language.zig
··· 1 + const std = @import("std"); 2 + 3 + const Formatter = @import("Formatter.zig"); 4 + 5 + const Language = @This(); 6 + 7 + formatter: Formatter,
+48
src/libfn/formatters/ZigFmt.zig
··· 1 + const std = @import("std"); 2 + 3 + const Formatter = @import("../Formatter.zig"); 4 + 5 + const ZigFmt = @This(); 6 + 7 + pub fn init() Formatter { 8 + return .{ 9 + .vtable = &.{ 10 + .format = format, 11 + }, 12 + }; 13 + } 14 + 15 + pub fn format(f: *Formatter, gpa: std.mem.Allocator, input: []const u8) anyerror![]const u8 { 16 + _ = f; 17 + 18 + var child = std.process.Child.init(&.{ "zig", "fmt", "--stdin" }, gpa); 19 + child.stdin_behavior = .Pipe; 20 + child.stdout_behavior = .Pipe; 21 + child.stderr_behavior = .Pipe; 22 + 23 + try child.spawn(); 24 + 25 + const stdin = child.stdin.?; 26 + 27 + var buffer: [4096]u8 = undefined; 28 + var w = stdin.writer(&buffer); 29 + _ = try w.interface.write(input); 30 + try w.interface.flush(); 31 + 32 + child.stdin.?.close(); 33 + child.stdin = null; 34 + 35 + var out: std.ArrayList(u8) = .empty; 36 + var err: std.ArrayList(u8) = .empty; 37 + defer out.deinit(gpa); 38 + defer err.deinit(gpa); 39 + 40 + // FIXME: maxInt(usize) is probably excessive here? 41 + try child.collectOutput(gpa, &out, &err, std.math.maxInt(usize)); 42 + 43 + const result = try child.wait(); 44 + 45 + if (result.Exited == 0) return out.toOwnedSlice(gpa); 46 + 47 + return error.FailedToFormat; 48 + }
+12
src/libfn/languages/Zig.zig
··· 1 + const std = @import("std"); 2 + 3 + const Zig = @This(); 4 + 5 + const Language = @import("../Language.zig"); 6 + const ZigFmt = @import("../formatters/ZigFmt.zig"); 7 + 8 + pub fn init() Language { 9 + return .{ 10 + .formatter = ZigFmt.init(), 11 + }; 12 + }
+2 -2
src/tui/main.zig
··· 174 174 }); 175 175 176 176 if (menu_bar_action) |a| { 177 - if (a.id == .file_menu_save and a.action == .clicked) try state.editor.saveFile(); 177 + if (a.id == .file_menu_save and a.action == .clicked) try state.editor.saveFile(state.gpa); 178 178 if (a.id == .file_menu_quit and a.action == .clicked) return .stop; 179 179 } 180 180 } ··· 252 252 } 253 253 } 254 254 255 - if (key.matches('s', .{ .super = true })) try state.editor.saveFile(); 255 + if (key.matches('s', .{ .super = true })) try state.editor.saveFile(state.gpa); 256 256 }, 257 257 .mouse => |mouse| if (container.hasMouse(mouse)) |_| { 258 258 if (mouse.button == .left and mouse.type == .press) {
+3
test.zig
··· 1 + pub fn main() void { 2 + return; 3 + }