diff options
Diffstat (limited to 'src/Editor.zig')
| -rw-r--r-- | src/Editor.zig | 214 |
1 files changed, 161 insertions, 53 deletions
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; | |||
| 5 | const ArrayList = std.ArrayList; | 5 | const ArrayList = std.ArrayList; |
| 6 | const Buffer = @import("Buffer.zig"); | 6 | const Buffer = @import("Buffer.zig"); |
| 7 | const Editor = @This(); | 7 | const Editor = @This(); |
| 8 | const files = @import("files.zig"); | ||
| 8 | const Key = @import("key.zig").Key; | 9 | const Key = @import("key.zig").Key; |
| 9 | const key_state = @import("key_state.zig"); | 10 | const key_state = @import("key_state.zig"); |
| 10 | const KeyState = key_state.KeyState; | 11 | const KeyState = key_state.KeyState; |
| @@ -25,6 +26,7 @@ statusmsg_time: i64, | |||
| 25 | 26 | ||
| 26 | current_state: KeyState, | 27 | current_state: KeyState, |
| 27 | 28 | ||
| 29 | key_buffer: ArrayList(Key), | ||
| 28 | should_exit: bool, | 30 | should_exit: bool, |
| 29 | 31 | ||
| 30 | pub fn init(allocator: Allocator) !Editor { | 32 | pub fn init(allocator: Allocator) !Editor { |
| @@ -42,6 +44,7 @@ pub fn init(allocator: Allocator) !Editor { | |||
| 42 | 44 | ||
| 43 | .current_state = key_state.defaultState, | 45 | .current_state = key_state.defaultState, |
| 44 | 46 | ||
| 47 | .key_buffer = ArrayList(Key).init(allocator), | ||
| 45 | .should_exit = false, | 48 | .should_exit = false, |
| 46 | }; | 49 | }; |
| 47 | errdefer self.deinit(); | 50 | errdefer self.deinit(); |
| @@ -66,6 +69,8 @@ pub fn deinit(self: *Editor) void { | |||
| 66 | self.allocator.free(statusmsg); | 69 | self.allocator.free(statusmsg); |
| 67 | } | 70 | } |
| 68 | 71 | ||
| 72 | self.key_buffer.deinit(); | ||
| 73 | |||
| 69 | self.* = undefined; | 74 | self.* = undefined; |
| 70 | } | 75 | } |
| 71 | 76 | ||
| @@ -80,6 +85,19 @@ pub fn getBuffer(self: Editor, name: []const u8) ?*Buffer { | |||
| 80 | return self.buffers.getPtr(name); | 85 | return self.buffers.getPtr(name); |
| 81 | } | 86 | } |
| 82 | 87 | ||
| 88 | pub fn getBufferByPath(self: Editor, path: []const u8) ?*Buffer { | ||
| 89 | var it = self.buffers.valueIterator(); | ||
| 90 | while (it.next()) |buffer| { | ||
| 91 | if (buffer.file_path) |buffer_path| { | ||
| 92 | if (std.mem.eql(u8, path, buffer_path)) { | ||
| 93 | return buffer; | ||
| 94 | } | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 98 | return null; | ||
| 99 | } | ||
| 100 | |||
| 83 | pub fn getOrPutBuffer(self: *Editor, name: []const u8) !*Buffer { | 101 | pub fn getOrPutBuffer(self: *Editor, name: []const u8) !*Buffer { |
| 84 | const get_or_put_res = try self.buffers.getOrPut(name); | 102 | const get_or_put_res = try self.buffers.getOrPut(name); |
| 85 | if (get_or_put_res.found_existing) { | 103 | if (get_or_put_res.found_existing) { |
| @@ -95,6 +113,17 @@ pub fn getOrPutBuffer(self: *Editor, name: []const u8) !*Buffer { | |||
| 95 | return get_or_put_res.value_ptr; | 113 | return get_or_put_res.value_ptr; |
| 96 | } | 114 | } |
| 97 | 115 | ||
| 116 | pub fn getUniqueBufferName(self: Editor, allocator: Allocator, base_name: []const u8) ![]u8 { | ||
| 117 | var unique_name = try allocator.dupe(u8, base_name); | ||
| 118 | var idx: usize = 1; | ||
| 119 | while (self.hasBuffer(unique_name)) : (idx += 1) { | ||
| 120 | allocator.free(unique_name); | ||
| 121 | unique_name = try std.fmt.allocPrint(allocator, "{s}<{}>", .{ base_name, idx }); | ||
| 122 | } | ||
| 123 | |||
| 124 | return unique_name; | ||
| 125 | } | ||
| 126 | |||
| 98 | pub fn hasBuffer(self: Editor, name: []const u8) bool { | 127 | pub fn hasBuffer(self: Editor, name: []const u8) bool { |
| 99 | return self.getBuffer(name) != null; | 128 | return self.getBuffer(name) != null; |
| 100 | } | 129 | } |
| @@ -107,7 +136,7 @@ pub fn killCurrentBuffer(self: *Editor) !bool { | |||
| 107 | } | 136 | } |
| 108 | } | 137 | } |
| 109 | 138 | ||
| 110 | const entry_to_kill = self.buffers.fetchRemove(self.buffer.name).?; | 139 | const entry_to_kill = self.buffers.fetchRemove(self.buffer.short_name).?; |
| 111 | self.allocator.free(entry_to_kill.key); | 140 | self.allocator.free(entry_to_kill.key); |
| 112 | entry_to_kill.value.deinit(); | 141 | entry_to_kill.value.deinit(); |
| 113 | 142 | ||
| @@ -120,26 +149,26 @@ pub fn killCurrentBuffer(self: *Editor) !bool { | |||
| 120 | return true; | 149 | return true; |
| 121 | } | 150 | } |
| 122 | 151 | ||
| 123 | pub fn open(self: *Editor, fname: []const u8) !void { | 152 | pub fn open(self: *Editor, name: []const u8) !void { |
| 124 | if (self.hasBuffer(fname)) { | 153 | const file_path = try files.resolvePath(self.allocator, name); |
| 125 | if (!try self.promptYN("A file with such name is already open. Open anyways?")) { | 154 | defer self.allocator.free(file_path); |
| 126 | return; | 155 | |
| 127 | } | 156 | if (self.getBufferByPath(file_path)) |buffer| { |
| 157 | self.buffer = buffer; | ||
| 158 | return; | ||
| 128 | } | 159 | } |
| 129 | 160 | ||
| 130 | self.buffer = try self.getOrPutBuffer(fname); | 161 | const file_name = std.fs.path.basename(file_path); |
| 131 | // TODO: If already was dirty, ask again | 162 | self.buffer = try self.putNewBuffer(file_name); |
| 132 | self.buffer.has_file = true; | 163 | self.buffer.file_path = try self.allocator.dupe(u8, file_path); |
| 133 | 164 | ||
| 134 | try self.buffer.selectSyntaxHighlighting(); | 165 | try self.buffer.selectSyntaxHighlighting(); |
| 135 | self.buffer.deleteAllRows(); | 166 | std.debug.assert(self.buffer.rows.items.len == 0); |
| 136 | 167 | ||
| 137 | const file = std.fs.cwd().openFile(fname, .{ .read = true }) catch |err| switch (err) { | 168 | const file = std.fs.openFileAbsolute(file_path, .{ .read = true }) catch |err| switch (err) { |
| 138 | error.FileNotFound => { | 169 | error.FileNotFound => { |
| 139 | try self.setStatusMessage("Creating a new file...", .{}); | 170 | try self.setStatusMessage("Will create a new file on save...", .{}); |
| 140 | self.buffer.dirty = true; | 171 | self.buffer.dirty = true; |
| 141 | const file = try std.fs.cwd().createFile(fname, .{ .read = true }); | ||
| 142 | file.close(); | ||
| 143 | return; | 172 | return; |
| 144 | }, | 173 | }, |
| 145 | else => return err, | 174 | else => return err, |
| @@ -149,6 +178,7 @@ pub fn open(self: *Editor, fname: []const u8) !void { | |||
| 149 | var buffered_reader = std.io.bufferedReader(file.reader()); | 178 | var buffered_reader = std.io.bufferedReader(file.reader()); |
| 150 | const reader = buffered_reader.reader(); | 179 | const reader = buffered_reader.reader(); |
| 151 | 180 | ||
| 181 | // TODO: Limiting lines to 4096 characters | ||
| 152 | while (try reader.readUntilDelimiterOrEofAlloc(self.allocator, '\n', 4096)) |line| { | 182 | while (try reader.readUntilDelimiterOrEofAlloc(self.allocator, '\n', 4096)) |line| { |
| 153 | defer self.allocator.free(line); | 183 | defer self.allocator.free(line); |
| 154 | 184 | ||
| @@ -168,7 +198,7 @@ pub fn openFile(self: *Editor) !void { | |||
| 168 | } | 198 | } |
| 169 | 199 | ||
| 170 | pub fn processKeypress(self: *Editor) !void { | 200 | pub fn processKeypress(self: *Editor) !void { |
| 171 | const key = try readKey(); | 201 | const key = try self.readKey(); |
| 172 | try self.current_state(self, self.buffer, key); | 202 | try self.current_state(self, self.buffer, key); |
| 173 | } | 203 | } |
| 174 | 204 | ||
| @@ -193,7 +223,7 @@ pub fn promptEx( | |||
| 193 | 223 | ||
| 194 | // TODO: Navigation | 224 | // TODO: Navigation |
| 195 | // TODO: Draw the cursor | 225 | // TODO: Draw the cursor |
| 196 | const key = try readKey(); | 226 | const key = try self.readKey(); |
| 197 | switch (key) { | 227 | switch (key) { |
| 198 | Key.delete, Key.backspace => _ = buf.popOrNull(), | 228 | Key.delete, Key.backspace => _ = buf.popOrNull(), |
| 199 | Key.ctrl('g') => { | 229 | Key.ctrl('g') => { |
| @@ -260,9 +290,21 @@ pub fn putBuffer(self: *Editor, buf_name: []const u8) !*Buffer { | |||
| 260 | prev_kv.value.deinit(); | 290 | prev_kv.value.deinit(); |
| 261 | } | 291 | } |
| 262 | 292 | ||
| 293 | // TODO: This feels inefficient | ||
| 263 | return self.buffers.getPtr(duped_name).?; | 294 | return self.buffers.getPtr(duped_name).?; |
| 264 | } | 295 | } |
| 265 | 296 | ||
| 297 | /// This always adds a new buffer, name might be modified to avoid clashes. | ||
| 298 | pub fn putNewBuffer(self: *Editor, name: []const u8) !*Buffer { | ||
| 299 | const unique_name = try self.getUniqueBufferName(self.allocator, name); | ||
| 300 | errdefer self.allocator.free(unique_name); | ||
| 301 | |||
| 302 | try self.buffers.putNoClobber(unique_name, try Buffer.init(self.allocator, unique_name)); | ||
| 303 | |||
| 304 | // TODO: This feels inefficient | ||
| 305 | return self.buffers.getPtr(unique_name).?; | ||
| 306 | } | ||
| 307 | |||
| 266 | pub fn refreshScreen(self: *Editor) !void { | 308 | pub fn refreshScreen(self: *Editor) !void { |
| 267 | self.buffer.scroll(self.screenrows, self.screencols); | 309 | self.buffer.scroll(self.screenrows, self.screencols); |
| 268 | 310 | ||
| @@ -392,37 +434,50 @@ fn parseUnsignedOptDefault(comptime T: type, buf_opt: ?[]const u8, radix: u8, de | |||
| 392 | } | 434 | } |
| 393 | } | 435 | } |
| 394 | 436 | ||
| 395 | fn readKey() !Key { | 437 | fn readByte(reader: std.fs.File) !?u8 { |
| 396 | const std_in = std.io.getStdIn(); | 438 | var buf = [_]u8{undefined}; |
| 397 | 439 | if (1 != try reader.read(&buf)) { | |
| 398 | var buf = [_]u8{undefined} ** 3; | 440 | return null; |
| 399 | // No we do not care about possible EOF on stdin, don't run the editor with | 441 | } else { |
| 400 | // redirected stdin | 442 | return buf[0]; |
| 401 | while (1 != try std_in.read(buf[0..1])) { | ||
| 402 | try std.os.sched_yield(); // :) | ||
| 403 | } | 443 | } |
| 444 | } | ||
| 404 | 445 | ||
| 405 | if (buf[0] != '\x1b') { | 446 | fn readByteBlocking(reader: std.fs.File) !u8 { |
| 406 | return @intToEnum(Key, buf[0]); | 447 | // No we do not care about possible EOF on stdin, don't run the editor with redirected stdin. |
| 448 | var char = try readByte(reader); | ||
| 449 | while (char == null) : (char = try readByte(reader)) { | ||
| 450 | std.os.sched_yield() catch {}; // :) | ||
| 407 | } | 451 | } |
| 408 | 452 | ||
| 409 | if (1 != try std_in.read(buf[0..1])) { | 453 | return char.?; |
| 410 | return Key.escape; | 454 | } |
| 455 | |||
| 456 | fn readKey(self: *Editor) !Key { | ||
| 457 | if (self.key_buffer.items.len > 0) { | ||
| 458 | return self.key_buffer.pop(); | ||
| 411 | } | 459 | } |
| 412 | 460 | ||
| 413 | if (buf[0] == '[') { | 461 | const std_in = std.io.getStdIn(); |
| 414 | if (1 != try std_in.read(buf[1..2])) { | 462 | |
| 415 | return Key.meta('['); | 463 | const char1 = try readByteBlocking(std_in); |
| 416 | } | 464 | if (char1 != '\x1b') { |
| 465 | return Key.char(char1); | ||
| 466 | } | ||
| 417 | 467 | ||
| 418 | if (buf[1] >= '0' and buf[1] <= '9') { | 468 | // TODO: This is a bad way of parsing. |
| 419 | if (1 != try std_in.read(buf[2..3])) { | 469 | const char2 = (try readByte(std_in)) orelse { return Key.escape; }; |
| 420 | // TODO: Multiple key return support? | 470 | if (char2 == '[') { |
| 471 | const char3 = (try readByte(std_in)) orelse { return Key.meta('['); }; | ||
| 472 | if (char3 >= '0' and char3 <= '9') { | ||
| 473 | const char4 = (try readByte(std_in)) orelse { | ||
| 474 | std.log.err("Unknown terminal sequence '^[[{c}'", .{char3}); | ||
| 475 | try self.key_buffer.append(Key.char(char3)); | ||
| 421 | return Key.meta('['); | 476 | return Key.meta('['); |
| 422 | } | 477 | }; |
| 423 | 478 | ||
| 424 | if (buf[2] == '~') { | 479 | if (char4 == '~') { |
| 425 | return switch (buf[1]) { | 480 | return switch (char3) { |
| 426 | '1' => Key.home, | 481 | '1' => Key.home, |
| 427 | '3' => Key.delete, | 482 | '3' => Key.delete, |
| 428 | '4' => Key.end, | 483 | '4' => Key.end, |
| @@ -430,37 +485,90 @@ fn readKey() !Key { | |||
| 430 | '6' => Key.page_down, | 485 | '6' => Key.page_down, |
| 431 | '7' => Key.home, | 486 | '7' => Key.home, |
| 432 | '8' => Key.end, | 487 | '8' => Key.end, |
| 433 | // TODO: Multiple key return support | 488 | else => { |
| 434 | else => Key.meta('['), | 489 | std.log.err("Unknown terminal sequence '^[[{c}~'", .{char3}); |
| 490 | try self.key_buffer.append(Key.char('~')); | ||
| 491 | try self.key_buffer.append(Key.char(char3)); | ||
| 492 | return Key.meta('['); | ||
| 493 | }, | ||
| 494 | }; | ||
| 495 | } else if (char4 == ';' and char3 == '1') { | ||
| 496 | const char5 = (try readByte(std_in)) orelse { | ||
| 497 | std.log.err("Unknown terminal sequence '^[[1;'", .{}); | ||
| 498 | try self.key_buffer.append(Key.char(';')); | ||
| 499 | try self.key_buffer.append(Key.char('1')); | ||
| 500 | return Key.meta('['); | ||
| 435 | }; | 501 | }; |
| 502 | |||
| 503 | if (char5 == '5') { | ||
| 504 | const char6 = (try readByte(std_in)) orelse { | ||
| 505 | std.log.err("Unknown terminal sequence '^[[1;5'", .{}); | ||
| 506 | try self.key_buffer.append(Key.char('5')); | ||
| 507 | try self.key_buffer.append(Key.char(';')); | ||
| 508 | try self.key_buffer.append(Key.char('1')); | ||
| 509 | return Key.meta('['); | ||
| 510 | }; | ||
| 511 | |||
| 512 | return switch (char6) { | ||
| 513 | 'A' => Key.ctrl_up, | ||
| 514 | 'B' => Key.ctrl_down, | ||
| 515 | 'C' => Key.ctrl_right, | ||
| 516 | 'D' => Key.ctrl_left, | ||
| 517 | 'F' => Key.ctrl_end, | ||
| 518 | 'H' => Key.ctrl_home, | ||
| 519 | else => { | ||
| 520 | std.log.err("Unknown terminal sequence '^[[1;5{c}'", .{char6}); | ||
| 521 | try self.key_buffer.append(Key.char(char6)); | ||
| 522 | try self.key_buffer.append(Key.char('5')); | ||
| 523 | try self.key_buffer.append(Key.char(';')); | ||
| 524 | try self.key_buffer.append(Key.char('1')); | ||
| 525 | return Key.meta('['); | ||
| 526 | }, | ||
| 527 | }; | ||
| 528 | } else { | ||
| 529 | std.log.err("Unknown terminal sequence '^[[1;{c}'", .{char5}); | ||
| 530 | try self.key_buffer.append(Key.char(char5)); | ||
| 531 | try self.key_buffer.append(Key.char(';')); | ||
| 532 | try self.key_buffer.append(Key.char('1')); | ||
| 533 | return Key.meta('['); | ||
| 534 | } | ||
| 436 | } else { | 535 | } else { |
| 437 | // TODO: Multiple key return support | 536 | std.log.err("Unknown terminal sequence '^[[{c}{c}'", .{char3, char4}); |
| 537 | try self.key_buffer.append(Key.char(char4)); | ||
| 538 | try self.key_buffer.append(Key.char(char3)); | ||
| 438 | return Key.meta('['); | 539 | return Key.meta('['); |
| 439 | } | 540 | } |
| 440 | } else { | 541 | } else { |
| 441 | return switch (buf[1]) { | 542 | return switch (char3) { |
| 442 | 'A' => Key.up, | 543 | 'A' => Key.up, |
| 443 | 'B' => Key.down, | 544 | 'B' => Key.down, |
| 444 | 'C' => Key.right, | 545 | 'C' => Key.right, |
| 445 | 'D' => Key.left, | 546 | 'D' => Key.left, |
| 446 | 'F' => Key.end, | 547 | 'F' => Key.end, |
| 447 | 'H' => Key.home, | 548 | 'H' => Key.home, |
| 448 | // TODO: Multiple key return support | 549 | else => { |
| 449 | else => Key.meta('['), | 550 | std.log.err("Unknown terminal sequence '^[[{c}'", .{char3}); |
| 551 | try self.key_buffer.append(Key.char(char3)); | ||
| 552 | return Key.meta('['); | ||
| 553 | }, | ||
| 450 | }; | 554 | }; |
| 451 | } | 555 | } |
| 452 | } else if (buf[0] == 'O') { | 556 | } else if (char2 == 'O') { |
| 453 | if (1 != try std_in.read(buf[1..2])) { | 557 | const char3 = (try readByte(std_in)) orelse { return Key.meta('O'); }; |
| 454 | return Key.meta('O'); | 558 | return switch (char3) { |
| 455 | } | ||
| 456 | |||
| 457 | return switch (buf[1]) { | ||
| 458 | 'F' => Key.end, | 559 | 'F' => Key.end, |
| 459 | 'H' => Key.home, | 560 | 'H' => Key.home, |
| 460 | // TODO: Multiple key return support | 561 | else => { |
| 461 | else => Key.meta('O'), | 562 | std.log.err("Unknown terminal sequence '^[O{c}'", .{char3}); |
| 563 | try self.key_buffer.append(Key.char(char3)); | ||
| 564 | return Key.meta('O'); | ||
| 565 | }, | ||
| 462 | }; | 566 | }; |
| 463 | } else { | 567 | } else { |
| 464 | return Key.meta(buf[0]); | 568 | return Key.meta(char2); |
| 465 | } | 569 | } |
| 466 | } | 570 | } |
| 571 | // C-<page up> = ^[[5;5~ | ||
| 572 | // C-<page down> = ^[[6;5 | ||
| 573 | |||
| 574 | // 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 | ||