From f03a4b178eacd13226eaaba5f8d10892ccf78711 Mon Sep 17 00:00:00 2001 From: Uko Kokņevičs Date: Sat, 1 Jan 2022 23:57:41 +0200 Subject: changes --- src/Editor.zig | 214 +++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 161 insertions(+), 53 deletions(-) (limited to 'src/Editor.zig') diff --git a/src/Editor.zig b/src/Editor.zig index 4d45b77..42b5619 100644 --- a/src/Editor.zig +++ b/src/Editor.zig @@ -5,6 +5,7 @@ const Allocator = std.mem.Allocator; const ArrayList = std.ArrayList; const Buffer = @import("Buffer.zig"); const Editor = @This(); +const files = @import("files.zig"); const Key = @import("key.zig").Key; const key_state = @import("key_state.zig"); const KeyState = key_state.KeyState; @@ -25,6 +26,7 @@ statusmsg_time: i64, current_state: KeyState, +key_buffer: ArrayList(Key), should_exit: bool, pub fn init(allocator: Allocator) !Editor { @@ -42,6 +44,7 @@ pub fn init(allocator: Allocator) !Editor { .current_state = key_state.defaultState, + .key_buffer = ArrayList(Key).init(allocator), .should_exit = false, }; errdefer self.deinit(); @@ -66,6 +69,8 @@ pub fn deinit(self: *Editor) void { self.allocator.free(statusmsg); } + self.key_buffer.deinit(); + self.* = undefined; } @@ -80,6 +85,19 @@ pub fn getBuffer(self: Editor, name: []const u8) ?*Buffer { return self.buffers.getPtr(name); } +pub fn getBufferByPath(self: Editor, path: []const u8) ?*Buffer { + var it = self.buffers.valueIterator(); + while (it.next()) |buffer| { + if (buffer.file_path) |buffer_path| { + if (std.mem.eql(u8, path, buffer_path)) { + return buffer; + } + } + } + + return null; +} + pub fn getOrPutBuffer(self: *Editor, name: []const u8) !*Buffer { const get_or_put_res = try self.buffers.getOrPut(name); if (get_or_put_res.found_existing) { @@ -95,6 +113,17 @@ pub fn getOrPutBuffer(self: *Editor, name: []const u8) !*Buffer { return get_or_put_res.value_ptr; } +pub fn getUniqueBufferName(self: Editor, allocator: Allocator, base_name: []const u8) ![]u8 { + var unique_name = try allocator.dupe(u8, base_name); + var idx: usize = 1; + while (self.hasBuffer(unique_name)) : (idx += 1) { + allocator.free(unique_name); + unique_name = try std.fmt.allocPrint(allocator, "{s}<{}>", .{ base_name, idx }); + } + + return unique_name; +} + pub fn hasBuffer(self: Editor, name: []const u8) bool { return self.getBuffer(name) != null; } @@ -107,7 +136,7 @@ pub fn killCurrentBuffer(self: *Editor) !bool { } } - const entry_to_kill = self.buffers.fetchRemove(self.buffer.name).?; + const entry_to_kill = self.buffers.fetchRemove(self.buffer.short_name).?; self.allocator.free(entry_to_kill.key); entry_to_kill.value.deinit(); @@ -120,26 +149,26 @@ pub fn killCurrentBuffer(self: *Editor) !bool { return true; } -pub fn open(self: *Editor, fname: []const u8) !void { - if (self.hasBuffer(fname)) { - if (!try self.promptYN("A file with such name is already open. Open anyways?")) { - return; - } +pub fn open(self: *Editor, name: []const u8) !void { + const file_path = try files.resolvePath(self.allocator, name); + defer self.allocator.free(file_path); + + if (self.getBufferByPath(file_path)) |buffer| { + self.buffer = buffer; + return; } - self.buffer = try self.getOrPutBuffer(fname); - // TODO: If already was dirty, ask again - self.buffer.has_file = true; + const file_name = std.fs.path.basename(file_path); + self.buffer = try self.putNewBuffer(file_name); + self.buffer.file_path = try self.allocator.dupe(u8, file_path); try self.buffer.selectSyntaxHighlighting(); - self.buffer.deleteAllRows(); + std.debug.assert(self.buffer.rows.items.len == 0); - const file = std.fs.cwd().openFile(fname, .{ .read = true }) catch |err| switch (err) { + const file = std.fs.openFileAbsolute(file_path, .{ .read = true }) catch |err| switch (err) { error.FileNotFound => { - try self.setStatusMessage("Creating a new file...", .{}); + try self.setStatusMessage("Will create a new file on save...", .{}); self.buffer.dirty = true; - const file = try std.fs.cwd().createFile(fname, .{ .read = true }); - file.close(); return; }, else => return err, @@ -149,6 +178,7 @@ pub fn open(self: *Editor, fname: []const u8) !void { var buffered_reader = std.io.bufferedReader(file.reader()); const reader = buffered_reader.reader(); + // TODO: Limiting lines to 4096 characters while (try reader.readUntilDelimiterOrEofAlloc(self.allocator, '\n', 4096)) |line| { defer self.allocator.free(line); @@ -168,7 +198,7 @@ pub fn openFile(self: *Editor) !void { } pub fn processKeypress(self: *Editor) !void { - const key = try readKey(); + const key = try self.readKey(); try self.current_state(self, self.buffer, key); } @@ -193,7 +223,7 @@ pub fn promptEx( // TODO: Navigation // TODO: Draw the cursor - const key = try readKey(); + const key = try self.readKey(); switch (key) { Key.delete, Key.backspace => _ = buf.popOrNull(), Key.ctrl('g') => { @@ -260,9 +290,21 @@ pub fn putBuffer(self: *Editor, buf_name: []const u8) !*Buffer { prev_kv.value.deinit(); } + // TODO: This feels inefficient return self.buffers.getPtr(duped_name).?; } +/// This always adds a new buffer, name might be modified to avoid clashes. +pub fn putNewBuffer(self: *Editor, name: []const u8) !*Buffer { + const unique_name = try self.getUniqueBufferName(self.allocator, name); + errdefer self.allocator.free(unique_name); + + try self.buffers.putNoClobber(unique_name, try Buffer.init(self.allocator, unique_name)); + + // TODO: This feels inefficient + return self.buffers.getPtr(unique_name).?; +} + pub fn refreshScreen(self: *Editor) !void { self.buffer.scroll(self.screenrows, self.screencols); @@ -392,37 +434,50 @@ fn parseUnsignedOptDefault(comptime T: type, buf_opt: ?[]const u8, radix: u8, de } } -fn readKey() !Key { - const std_in = std.io.getStdIn(); - - var buf = [_]u8{undefined} ** 3; - // No we do not care about possible EOF on stdin, don't run the editor with - // redirected stdin - while (1 != try std_in.read(buf[0..1])) { - try std.os.sched_yield(); // :) +fn readByte(reader: std.fs.File) !?u8 { + var buf = [_]u8{undefined}; + if (1 != try reader.read(&buf)) { + return null; + } else { + return buf[0]; } +} - if (buf[0] != '\x1b') { - return @intToEnum(Key, buf[0]); +fn readByteBlocking(reader: std.fs.File) !u8 { + // No we do not care about possible EOF on stdin, don't run the editor with redirected stdin. + var char = try readByte(reader); + while (char == null) : (char = try readByte(reader)) { + std.os.sched_yield() catch {}; // :) } - if (1 != try std_in.read(buf[0..1])) { - return Key.escape; + return char.?; +} + +fn readKey(self: *Editor) !Key { + if (self.key_buffer.items.len > 0) { + return self.key_buffer.pop(); } - if (buf[0] == '[') { - if (1 != try std_in.read(buf[1..2])) { - return Key.meta('['); - } + const std_in = std.io.getStdIn(); + + const char1 = try readByteBlocking(std_in); + if (char1 != '\x1b') { + return Key.char(char1); + } - if (buf[1] >= '0' and buf[1] <= '9') { - if (1 != try std_in.read(buf[2..3])) { - // TODO: Multiple key return support? + // TODO: This is a bad way of parsing. + const char2 = (try readByte(std_in)) orelse { return Key.escape; }; + if (char2 == '[') { + const char3 = (try readByte(std_in)) orelse { return Key.meta('['); }; + if (char3 >= '0' and char3 <= '9') { + const char4 = (try readByte(std_in)) orelse { + std.log.err("Unknown terminal sequence '^[[{c}'", .{char3}); + try self.key_buffer.append(Key.char(char3)); return Key.meta('['); - } + }; - if (buf[2] == '~') { - return switch (buf[1]) { + if (char4 == '~') { + return switch (char3) { '1' => Key.home, '3' => Key.delete, '4' => Key.end, @@ -430,37 +485,90 @@ fn readKey() !Key { '6' => Key.page_down, '7' => Key.home, '8' => Key.end, - // TODO: Multiple key return support - else => Key.meta('['), + else => { + std.log.err("Unknown terminal sequence '^[[{c}~'", .{char3}); + try self.key_buffer.append(Key.char('~')); + try self.key_buffer.append(Key.char(char3)); + return Key.meta('['); + }, + }; + } else if (char4 == ';' and char3 == '1') { + const char5 = (try readByte(std_in)) orelse { + std.log.err("Unknown terminal sequence '^[[1;'", .{}); + try self.key_buffer.append(Key.char(';')); + try self.key_buffer.append(Key.char('1')); + return Key.meta('['); }; + + if (char5 == '5') { + const char6 = (try readByte(std_in)) orelse { + std.log.err("Unknown terminal sequence '^[[1;5'", .{}); + try self.key_buffer.append(Key.char('5')); + try self.key_buffer.append(Key.char(';')); + try self.key_buffer.append(Key.char('1')); + return Key.meta('['); + }; + + return switch (char6) { + 'A' => Key.ctrl_up, + 'B' => Key.ctrl_down, + 'C' => Key.ctrl_right, + 'D' => Key.ctrl_left, + 'F' => Key.ctrl_end, + 'H' => Key.ctrl_home, + else => { + std.log.err("Unknown terminal sequence '^[[1;5{c}'", .{char6}); + try self.key_buffer.append(Key.char(char6)); + try self.key_buffer.append(Key.char('5')); + try self.key_buffer.append(Key.char(';')); + try self.key_buffer.append(Key.char('1')); + return Key.meta('['); + }, + }; + } else { + std.log.err("Unknown terminal sequence '^[[1;{c}'", .{char5}); + try self.key_buffer.append(Key.char(char5)); + try self.key_buffer.append(Key.char(';')); + try self.key_buffer.append(Key.char('1')); + return Key.meta('['); + } } else { - // TODO: Multiple key return support + std.log.err("Unknown terminal sequence '^[[{c}{c}'", .{char3, char4}); + try self.key_buffer.append(Key.char(char4)); + try self.key_buffer.append(Key.char(char3)); return Key.meta('['); } } else { - return switch (buf[1]) { + return switch (char3) { 'A' => Key.up, 'B' => Key.down, 'C' => Key.right, 'D' => Key.left, 'F' => Key.end, 'H' => Key.home, - // TODO: Multiple key return support - else => Key.meta('['), + else => { + std.log.err("Unknown terminal sequence '^[[{c}'", .{char3}); + try self.key_buffer.append(Key.char(char3)); + return Key.meta('['); + }, }; } - } else if (buf[0] == 'O') { - if (1 != try std_in.read(buf[1..2])) { - return Key.meta('O'); - } - - return switch (buf[1]) { + } else if (char2 == 'O') { + const char3 = (try readByte(std_in)) orelse { return Key.meta('O'); }; + return switch (char3) { 'F' => Key.end, 'H' => Key.home, - // TODO: Multiple key return support - else => Key.meta('O'), + else => { + std.log.err("Unknown terminal sequence '^[O{c}'", .{char3}); + try self.key_buffer.append(Key.char(char3)); + return Key.meta('O'); + }, }; } else { - return Key.meta(buf[0]); + return Key.meta(char2); } } +// C- = ^[[5;5~ +// C- = ^[[6;5 + +// S- adds ;2, M- adds ;3, S-M- adds ;4, C- adds ;5, S-C- adds ;6, M-C- adds ;7, S-M-C- adds ;8 -- cgit v1.2.3