summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Uko Kokņevičs2022-01-01 23:57:41 +0200
committerGravatar Uko Kokņevičs2022-01-01 23:57:41 +0200
commitf03a4b178eacd13226eaaba5f8d10892ccf78711 (patch)
tree09ebb7ac3e1750d3c03d3e7dad570a873f132907
parentUpdate make mode (diff)
downloades-f03a4b178eacd13226eaaba5f8d10892ccf78711.tar.gz
es-f03a4b178eacd13226eaaba5f8d10892ccf78711.tar.xz
es-f03a4b178eacd13226eaaba5f8d10892ccf78711.zip
changes
-rw-r--r--src/Buffer.zig116
-rw-r--r--src/Editor.zig214
-rw-r--r--src/files.zig21
-rw-r--r--src/key.zig24
-rw-r--r--src/key_state.zig3
5 files changed, 276 insertions, 102 deletions
diff --git a/src/Buffer.zig b/src/Buffer.zig
index f105c55..0c95e6e 100644
--- a/src/Buffer.zig
+++ b/src/Buffer.zig
@@ -6,6 +6,7 @@ const ArrayList = std.ArrayList;
6const Buffer = @This(); 6const Buffer = @This();
7const Config = @import("Config.zig"); 7const Config = @import("Config.zig");
8const File = std.fs.File; 8const File = std.fs.File;
9const files = @import("files.zig");
9const Editor = @import("Editor.zig"); 10const Editor = @import("Editor.zig");
10const Highlight = @import("highlight.zig").Highlight; 11const Highlight = @import("highlight.zig").Highlight;
11const Row = @import("Row.zig"); 12const Row = @import("Row.zig");
@@ -13,8 +14,8 @@ const Syntax = @import("Syntax.zig");
13 14
14allocator: Allocator, 15allocator: Allocator,
15 16
16// TODO: Short name & file name split 17file_path: ?[]u8,
17name: []u8, 18short_name: []u8,
18 19
19rows: ArrayList(Row), 20rows: ArrayList(Row),
20 21
@@ -26,7 +27,6 @@ rowoff: usize,
26coloff: usize, 27coloff: usize,
27 28
28dirty: bool, 29dirty: bool,
29has_file: bool,
30 30
31config: Config, 31config: Config,
32syntax: ?Syntax, 32syntax: ?Syntax,
@@ -41,7 +41,8 @@ pub fn init(allocator: Allocator, name: []const u8) !Buffer {
41 return Buffer{ 41 return Buffer{
42 .allocator = allocator, 42 .allocator = allocator,
43 43
44 .name = name_owned, 44 .file_path = null,
45 .short_name = name_owned,
45 46
46 .rows = ArrayList(Row).init(allocator), 47 .rows = ArrayList(Row).init(allocator),
47 48
@@ -53,7 +54,6 @@ pub fn init(allocator: Allocator, name: []const u8) !Buffer {
53 .coloff = 0, 54 .coloff = 0,
54 55
55 .dirty = false, 56 .dirty = false,
56 .has_file = false,
57 57
58 .config = config, 58 .config = config,
59 .syntax = null, 59 .syntax = null,
@@ -61,7 +61,11 @@ pub fn init(allocator: Allocator, name: []const u8) !Buffer {
61} 61}
62 62
63pub fn deinit(self: Buffer) void { 63pub fn deinit(self: Buffer) void {
64 self.allocator.free(self.name); 64 if (self.file_path) |file_path| {
65 self.allocator.free(file_path);
66 }
67
68 self.allocator.free(self.short_name);
65 69
66 for (self.rows.items) |row| { 70 for (self.rows.items) |row| {
67 row.deinit(); 71 row.deinit();
@@ -218,10 +222,14 @@ pub fn drawRows(self: Buffer, writer: anytype, screenrows: usize, screencols: us
218pub fn drawStatusBar(self: Buffer, writer: anytype, screencols: usize) !void { 222pub fn drawStatusBar(self: Buffer, writer: anytype, screencols: usize) !void {
219 try writer.writeAll("\x1b[m\x1b[7m"); 223 try writer.writeAll("\x1b[m\x1b[7m");
220 224
221 var name = if (self.name.len > 20) 225 var name = if (self.short_name.len > 20)
222 try std.fmt.allocPrint(self.allocator, "{s}...", .{self.name[0..17]}) 226 try std.fmt.allocPrint(
227 self.allocator,
228 "...{s}",
229 .{self.short_name[self.short_name.len - 17 ..]},
230 )
223 else 231 else
224 try self.allocator.dupe(u8, self.name); 232 try self.allocator.dupe(u8, self.short_name);
225 defer self.allocator.free(name); 233 defer self.allocator.free(name);
226 234
227 const modified = if (self.dirty) 235 const modified = if (self.dirty)
@@ -271,7 +279,7 @@ pub fn goToLine(self: *Buffer, editor: *Editor) !void {
271 return; 279 return;
272 }; 280 };
273 281
274 self.cy = std.math.min(line - 1, self.rows.items.len); 282 self.cy = std.math.clamp(line, 1, self.rows.items.len + 1) - 1;
275 if (self.cy == self.rows.items.len) { 283 if (self.cy == self.rows.items.len) {
276 self.cx = 0; 284 self.cx = 0;
277 } else { 285 } else {
@@ -422,54 +430,73 @@ pub fn recenterTopBottom(self: *Buffer, screenrows: usize) void {
422} 430}
423 431
424pub fn save(self: *Buffer, editor: *Editor) !void { 432pub fn save(self: *Buffer, editor: *Editor) !void {
425 if (!self.has_file) { 433 if (self.file_path == null) {
426 const fname = try editor.prompt("Save as"); 434 // TODO: Editor.prompt should take an Allocator
427 if (fname == null) { 435 const fname = (try editor.prompt("Save as")) orelse { return; };
428 return; 436 defer self.allocator.free(fname);
429 } 437
438 const file_path = try files.resolvePath(self.allocator, fname);
439 errdefer self.allocator.free(file_path);
430 440
431 self.allocator.free(self.name); 441 const file_name = std.fs.path.basename(file_path);
432 self.name = fname.?; 442 const short_name = try editor.getUniqueBufferName(self.allocator, file_name);
433 self.has_file = true; 443 errdefer self.allocator.free(short_name);
434 444
435 try self.selectSyntaxHighlighting(); 445 self.allocator.free(self.short_name);
446 self.short_name = short_name;
447 self.file_path = file_path;
448
449 // TODO: Do I want to do this?
450 // try self.selectSyntaxHighlighting();
436 } 451 }
437 452
438 // TODO: Add a config value for this 453 // TODO: Add a config value for this
439 try self.cleanWhiteSpace(); 454 try self.cleanWhiteSpace();
440 455
441 const tmpname = try std.fmt.allocPrint(self.allocator, "{s}~{}", .{ self.name, std.time.milliTimestamp() }); 456 const file_path = self.file_path.?;
442 defer self.allocator.free(tmpname); 457
458 const tmp_path = try std.fmt.allocPrint(
459 self.allocator,
460 "{s}~{}",
461 .{ file_path, std.time.milliTimestamp() },
462 );
463 defer self.allocator.free(tmp_path);
443 464
444 const tmpfile = std.fs.cwd().createFile(tmpname, .{ .truncate = true }) catch |err| { 465 const tmp_file = std.fs.createFileAbsolute(tmp_path, .{ .truncate = true }) catch |err| {
445 try editor.setStatusMessage("Cannot open tempfile '{s}' for writing: {}", .{ tmpname, err }); 466 try editor.setStatusMessage(
467 "Cannot open tempfile '{s}' for writing: {}",
468 .{ tmp_path, err },
469 );
446 return; 470 return;
447 }; 471 };
448 defer tmpfile.close(); 472 defer tmp_file.close();
449 473
450 const stat = statFile(self.name) catch |err| { 474 const mode = statFileMode(file_path) catch |err| {
451 try editor.setStatusMessage("Couldn't stat file '{s}': {}", .{ self.name, err }); 475 try editor.setStatusMessage("Couldn't stat file '{s}': {}", .{ file_path, err });
452 return; 476 return;
453 }; 477 };
454 478
455 tmpfile.chmod(stat.mode) catch |err| { 479 tmp_file.chmod(mode) catch |err| {
456 try editor.setStatusMessage("Couldn't chmod tempfile '{s}': {}", .{ tmpname, err }); 480 try editor.setStatusMessage("Couldn't chmod tempfile '{s}': {}", .{ tmp_path, err });
457 return; 481 return;
458 }; 482 };
459 483
460 // TODO: chown 484 // TODO: chown
461 485
462 self.writeToFile(tmpfile) catch |err| { 486 self.writeToFile(tmp_file) catch |err| {
463 try editor.setStatusMessage("Couldn't write to tempfile '{s}': {}", .{ tmpname, err }); 487 try editor.setStatusMessage("Couldn't write to tempfile '{s}': {}", .{ tmp_path, err });
464 return; 488 return;
465 }; 489 };
466 490
467 std.fs.cwd().rename(tmpname, self.name) catch |err| { 491 std.fs.renameAbsolute(tmp_path, file_path) catch |err| {
468 try editor.setStatusMessage("Couldn't move '{s}' to '{s}': {}", .{ tmpname, self.name, err }); 492 try editor.setStatusMessage(
493 "Couldn't move '{s}' to '{s}': {}",
494 .{ tmp_path, file_path, err },
495 );
469 return; 496 return;
470 }; 497 };
471 498
472 try editor.setStatusMessage("Saved to '{s}'", .{self.name}); 499 try editor.setStatusMessage("Saved to '{s}'", .{file_path});
473 self.dirty = false; 500 self.dirty = false;
474} 501}
475 502
@@ -494,12 +521,17 @@ pub fn scroll(self: *Buffer, screenrows: usize, screencols: usize) void {
494 521
495pub fn selectSyntaxHighlighting(self: *Buffer) !void { 522pub fn selectSyntaxHighlighting(self: *Buffer) !void {
496 self.syntax = null; 523 self.syntax = null;
524 const name = if (self.file_path) |file_path|
525 std.fs.path.basename(file_path)
526 else
527 self.short_name;
497 528
498 const ext = if (std.mem.lastIndexOfScalar(u8, self.name, '.')) |idx| self.name[idx..] else null; 529 const ext = if (std.mem.lastIndexOfScalar(u8, name, '.')) |idx| name[idx..] else null;
499 for (Syntax.database) |syntax| { 530 for (Syntax.database) |syntax| {
500 for (syntax.filematch) |filematch| { 531 for (syntax.filematch) |filematch| {
501 const is_ext = filematch[0] == '.'; 532 const is_ext = filematch[0] == '.';
502 if ((is_ext and ext != null and std.mem.eql(u8, ext.?, filematch)) or (!is_ext and std.mem.eql(u8, self.name, filematch))) { 533 if ((is_ext and ext != null and std.mem.eql(u8, ext.?, filematch))
534 or (!is_ext and std.mem.eql(u8, name, filematch))) {
503 self.syntax = syntax; 535 self.syntax = syntax;
504 536
505 for (self.rows.items) |*row| { 537 for (self.rows.items) |*row| {
@@ -529,22 +561,14 @@ fn printWithLeftPadding(
529 try writer.writeAll(unpadded); 561 try writer.writeAll(unpadded);
530} 562}
531 563
532fn statFile(name: []const u8) !File.Stat { 564fn statFileMode(name: []const u8) !File.Mode {
533 const file = std.fs.cwd().openFile(name, .{}) catch |err| switch (err) { 565 const file = std.fs.cwd().openFile(name, .{}) catch |err| switch (err) {
534 error.FileNotFound => return File.Stat{ 566 error.FileNotFound => return 0o644,
535 .inode = 0,
536 .size = 0,
537 .mode = 0o644,
538 .kind = .File,
539 .atime = 0,
540 .mtime = 0,
541 .ctime = 0,
542 },
543 else => return err, 567 else => return err,
544 }; 568 };
545 defer file.close(); 569 defer file.close();
546 570
547 return file.stat(); 571 return (try file.stat()).mode;
548} 572}
549 573
550fn writeToFile(self: Buffer, file: File) !void { 574fn writeToFile(self: Buffer, file: File) !void {
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
diff --git a/src/files.zig b/src/files.zig
new file mode 100644
index 0000000..ed9e628
--- /dev/null
+++ b/src/files.zig
@@ -0,0 +1,21 @@
1const std = @import("std");
2
3const Allocator = std.mem.Allocator;
4
5pub fn resolvePath(allocator: Allocator, name: []const u8) ![]u8 {
6 return std.fs.cwd().realpathAlloc(allocator, name) catch |err| switch (err) {
7 error.FileNotFound => if (name.len == 0) {
8 return error.FileNotFound;
9 } else if (name[0] == std.fs.path.sep) {
10 // Already is an absolute path
11 return allocator.dupe(u8, name);
12 } else {
13 // TODO: if (name[0] == '~')
14 const cwd = try std.process.getCwdAlloc(allocator);
15 defer allocator.free(cwd);
16
17 return std.fmt.allocPrint(allocator, "{s}{s}{s}", .{cwd, std.fs.path.sep_str, name});
18 },
19 else => return err,
20 };
21}
diff --git a/src/key.zig b/src/key.zig
index 3a007fb..5376017 100644
--- a/src/key.zig
+++ b/src/key.zig
@@ -14,12 +14,22 @@ pub const Key = enum(u16) {
14 right, 14 right,
15 up, 15 up,
16 down, 16 down,
17 delete,
18 home, 17 home,
19 end, 18 end,
19 delete,
20 page_up, 20 page_up,
21 page_down, 21 page_down,
22 22
23 ctrl_left,
24 ctrl_right,
25 ctrl_up,
26 ctrl_down,
27 ctrl_home,
28 ctrl_end,
29 // ctrl_delete,
30 // ctrl_page_up,
31 // ctrl_page_down,
32
23 _, 33 _,
24 34
25 pub fn char(ch: u8) Key { 35 pub fn char(ch: u8) Key {
@@ -53,12 +63,22 @@ pub const Key = enum(u16) {
53 .right => std.fmt.formatBuf("<right>", options, writer), 63 .right => std.fmt.formatBuf("<right>", options, writer),
54 .up => std.fmt.formatBuf("<up>", options, writer), 64 .up => std.fmt.formatBuf("<up>", options, writer),
55 .down => std.fmt.formatBuf("<down>", options, writer), 65 .down => std.fmt.formatBuf("<down>", options, writer),
56 .delete => std.fmt.formatBuf("<delete>", options, writer),
57 .home => std.fmt.formatBuf("<home>", options, writer), 66 .home => std.fmt.formatBuf("<home>", options, writer),
58 .end => std.fmt.formatBuf("<end>", options, writer), 67 .end => std.fmt.formatBuf("<end>", options, writer),
68 .delete => std.fmt.formatBuf("<delete>", options, writer),
59 .page_up => std.fmt.formatBuf("<page-up>", options, writer), 69 .page_up => std.fmt.formatBuf("<page-up>", options, writer),
60 .page_down => std.fmt.formatBuf("<page-down>", options, writer), 70 .page_down => std.fmt.formatBuf("<page-down>", options, writer),
61 71
72 .ctrl_left => std.fmt.formatBuf("C-<left>", options, writer),
73 .ctrl_right => std.fmt.formatBuf("C-<right>", options, writer),
74 .ctrl_up => std.fmt.formatBuf("C-<up>", options, writer),
75 .ctrl_down => std.fmt.formatBuf("C-<down>", options, writer),
76 .ctrl_home => std.fmt.formatBuf("C-<home>", options, writer),
77 .ctrl_end => std.fmt.formatBuf("C-<end>", options, writer),
78 // ctrl_delete
79 // ctrl_page_up
80 // ctrl_page_down
81
62 _ => key.formatGeneric(options, writer), 82 _ => key.formatGeneric(options, writer),
63 }; 83 };
64 } 84 }
diff --git a/src/key_state.zig b/src/key_state.zig
index d477094..f1880ae 100644
--- a/src/key_state.zig
+++ b/src/key_state.zig
@@ -15,7 +15,8 @@ pub const Error = error{
15 std.fs.File.OpenError || 15 std.fs.File.OpenError ||
16 std.fs.File.ReadError || 16 std.fs.File.ReadError ||
17 std.fs.File.WriteError || 17 std.fs.File.WriteError ||
18 std.os.SchedYieldError; 18 std.os.GetCwdError ||
19 std.os.RealPathError;
19pub const KeyState = fn (*Editor, *Buffer, Key) Error!void; 20pub const KeyState = fn (*Editor, *Buffer, Key) Error!void;
20 21
21pub fn mgState(editor: *Editor, buf: *Buffer, key: Key) Error!void { 22pub fn mgState(editor: *Editor, buf: *Buffer, key: Key) Error!void {