summaryrefslogtreecommitdiff
path: root/src/Editor.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/Editor.zig')
-rw-r--r--src/Editor.zig214
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;
5const ArrayList = std.ArrayList; 5const ArrayList = std.ArrayList;
6const Buffer = @import("Buffer.zig"); 6const Buffer = @import("Buffer.zig");
7const Editor = @This(); 7const Editor = @This();
8const files = @import("files.zig");
8const Key = @import("key.zig").Key; 9const Key = @import("key.zig").Key;
9const key_state = @import("key_state.zig"); 10const key_state = @import("key_state.zig");
10const KeyState = key_state.KeyState; 11const KeyState = key_state.KeyState;
@@ -25,6 +26,7 @@ statusmsg_time: i64,
25 26
26current_state: KeyState, 27current_state: KeyState,
27 28
29key_buffer: ArrayList(Key),
28should_exit: bool, 30should_exit: bool,
29 31
30pub fn init(allocator: Allocator) !Editor { 32pub 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
88pub 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
83pub fn getOrPutBuffer(self: *Editor, name: []const u8) !*Buffer { 101pub 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
116pub 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
98pub fn hasBuffer(self: Editor, name: []const u8) bool { 127pub 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
123pub fn open(self: *Editor, fname: []const u8) !void { 152pub 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
170pub fn processKeypress(self: *Editor) !void { 200pub 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.
298pub 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
266pub fn refreshScreen(self: *Editor) !void { 308pub 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
395fn readKey() !Key { 437fn 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') { 446fn 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
456fn 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