summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Uko Kokņevičs2022-01-02 05:25:20 +0200
committerGravatar Uko Kokņevičs2022-01-02 05:25:20 +0200
commit963d1fa7bd2d74c951859f27e4fb01eb71a77e8d (patch)
treef8f0d360a2ad48e76531668ae12378652d03affb /src
parenthierarchy changes (diff)
downloades-963d1fa7bd2d74c951859f27e4fb01eb71a77e8d.tar.gz
es-963d1fa7bd2d74c951859f27e4fb01eb71a77e8d.tar.xz
es-963d1fa7bd2d74c951859f27e4fb01eb71a77e8d.zip
Improved input
Diffstat (limited to 'src')
-rw-r--r--src/Buffer.zig4
-rw-r--r--src/Editor.zig150
-rw-r--r--src/KeyReader.zig221
-rw-r--r--src/key.zig158
-rw-r--r--src/key_state.zig23
-rw-r--r--src/main.zig3
6 files changed, 359 insertions, 200 deletions
diff --git a/src/Buffer.zig b/src/Buffer.zig
index 7f6e7d7..7afdb05 100644
--- a/src/Buffer.zig
+++ b/src/Buffer.zig
@@ -141,6 +141,10 @@ pub fn deleteAllRows(self: *Buffer) void {
141} 141}
142 142
143pub fn deleteRow(self: *Buffer, at: usize) void { 143pub fn deleteRow(self: *Buffer, at: usize) void {
144 if (at == self.rows.items.len) {
145 return;
146 }
147
144 self.dirty = true; 148 self.dirty = true;
145 149
146 self.rows.orderedRemove(at).deinit(); 150 self.rows.orderedRemove(at).deinit();
diff --git a/src/Editor.zig b/src/Editor.zig
index 4b563ad..99ec932 100644
--- a/src/Editor.zig
+++ b/src/Editor.zig
@@ -6,6 +6,7 @@ const ArrayList = std.ArrayList;
6const Buffer = es.Buffer; 6const Buffer = es.Buffer;
7const Editor = @This(); 7const Editor = @This();
8const Key = es.Key; 8const Key = es.Key;
9const KeyReader = es.KeyReader;
9const KeyState = es.key_state.KeyState; 10const KeyState = es.key_state.KeyState;
10const StringBuilder = es.StringBuilder; 11const StringBuilder = es.StringBuilder;
11const StringHashMap = std.StringHashMap; 12const StringHashMap = std.StringHashMap;
@@ -23,7 +24,7 @@ statusmsg_time: i64,
23 24
24current_state: KeyState, 25current_state: KeyState,
25 26
26key_buffer: ArrayList(Key), 27key_reader: KeyReader,
27should_exit: bool, 28should_exit: bool,
28 29
29pub fn init(allocator: Allocator) !Editor { 30pub fn init(allocator: Allocator) !Editor {
@@ -41,7 +42,7 @@ pub fn init(allocator: Allocator) !Editor {
41 42
42 .current_state = es.key_state.defaultState, 43 .current_state = es.key_state.defaultState,
43 44
44 .key_buffer = ArrayList(Key).init(allocator), 45 .key_reader = KeyReader.init(allocator),
45 .should_exit = false, 46 .should_exit = false,
46 }; 47 };
47 errdefer self.deinit(); 48 errdefer self.deinit();
@@ -66,7 +67,7 @@ pub fn deinit(self: *Editor) void {
66 self.allocator.free(statusmsg); 67 self.allocator.free(statusmsg);
67 } 68 }
68 69
69 self.key_buffer.deinit(); 70 self.key_reader.deinit();
70 71
71 self.* = undefined; 72 self.* = undefined;
72} 73}
@@ -195,7 +196,7 @@ pub fn openFile(self: *Editor) !void {
195} 196}
196 197
197pub fn processKeypress(self: *Editor) !void { 198pub fn processKeypress(self: *Editor) !void {
198 const key = try self.readKey(); 199 const key = try self.key_reader.readKey();
199 try self.current_state(self, self.buffer, key); 200 try self.current_state(self, self.buffer, key);
200} 201}
201 202
@@ -220,7 +221,7 @@ pub fn promptEx(
220 221
221 // TODO: Navigation 222 // TODO: Navigation
222 // TODO: Draw the cursor 223 // TODO: Draw the cursor
223 const key = try self.readKey(); 224 const key = try self.key_reader.readKey();
224 switch (key) { 225 switch (key) {
225 Key.delete, Key.backspace => _ = buf.popOrNull(), 226 Key.delete, Key.backspace => _ = buf.popOrNull(),
226 Key.ctrl('g') => { 227 Key.ctrl('g') => {
@@ -430,142 +431,3 @@ fn parseUnsignedOptDefault(comptime T: type, buf_opt: ?[]const u8, radix: u8, de
430 return default; 431 return default;
431 } 432 }
432} 433}
433
434fn readByte(reader: std.fs.File) !?u8 {
435 var buf = [_]u8{undefined};
436 if (1 != try reader.read(&buf)) {
437 return null;
438 } else {
439 return buf[0];
440 }
441}
442
443fn readByteBlocking(reader: std.fs.File) !u8 {
444 // No we do not care about possible EOF on stdin, don't run the editor with redirected stdin.
445 var char = try readByte(reader);
446 while (char == null) : (char = try readByte(reader)) {
447 std.os.sched_yield() catch {}; // :)
448 }
449
450 return char.?;
451}
452
453fn readKey(self: *Editor) !Key {
454 if (self.key_buffer.items.len > 0) {
455 return self.key_buffer.pop();
456 }
457
458 const std_in = std.io.getStdIn();
459
460 const char1 = try readByteBlocking(std_in);
461 if (char1 != '\x1b') {
462 return Key.char(char1);
463 }
464
465 // TODO: This is a bad way of parsing.
466 const char2 = (try readByte(std_in)) orelse { return Key.escape; };
467 if (char2 == '[') {
468 const char3 = (try readByte(std_in)) orelse { return Key.meta('['); };
469 if (char3 >= '0' and char3 <= '9') {
470 const char4 = (try readByte(std_in)) orelse {
471 std.log.err("Unknown terminal sequence '^[[{c}'", .{char3});
472 try self.key_buffer.append(Key.char(char3));
473 return Key.meta('[');
474 };
475
476 if (char4 == '~') {
477 return switch (char3) {
478 '1' => Key.home,
479 '3' => Key.delete,
480 '4' => Key.end,
481 '5' => Key.page_up,
482 '6' => Key.page_down,
483 '7' => Key.home,
484 '8' => Key.end,
485 else => {
486 std.log.err("Unknown terminal sequence '^[[{c}~'", .{char3});
487 try self.key_buffer.append(Key.char('~'));
488 try self.key_buffer.append(Key.char(char3));
489 return Key.meta('[');
490 },
491 };
492 } else if (char4 == ';' and char3 == '1') {
493 const char5 = (try readByte(std_in)) orelse {
494 std.log.err("Unknown terminal sequence '^[[1;'", .{});
495 try self.key_buffer.append(Key.char(';'));
496 try self.key_buffer.append(Key.char('1'));
497 return Key.meta('[');
498 };
499
500 if (char5 == '5') {
501 const char6 = (try readByte(std_in)) orelse {
502 std.log.err("Unknown terminal sequence '^[[1;5'", .{});
503 try self.key_buffer.append(Key.char('5'));
504 try self.key_buffer.append(Key.char(';'));
505 try self.key_buffer.append(Key.char('1'));
506 return Key.meta('[');
507 };
508
509 return switch (char6) {
510 'A' => Key.ctrl_up,
511 'B' => Key.ctrl_down,
512 'C' => Key.ctrl_right,
513 'D' => Key.ctrl_left,
514 'F' => Key.ctrl_end,
515 'H' => Key.ctrl_home,
516 else => {
517 std.log.err("Unknown terminal sequence '^[[1;5{c}'", .{char6});
518 try self.key_buffer.append(Key.char(char6));
519 try self.key_buffer.append(Key.char('5'));
520 try self.key_buffer.append(Key.char(';'));
521 try self.key_buffer.append(Key.char('1'));
522 return Key.meta('[');
523 },
524 };
525 } else {
526 std.log.err("Unknown terminal sequence '^[[1;{c}'", .{char5});
527 try self.key_buffer.append(Key.char(char5));
528 try self.key_buffer.append(Key.char(';'));
529 try self.key_buffer.append(Key.char('1'));
530 return Key.meta('[');
531 }
532 } else {
533 std.log.err("Unknown terminal sequence '^[[{c}{c}'", .{char3, char4});
534 try self.key_buffer.append(Key.char(char4));
535 try self.key_buffer.append(Key.char(char3));
536 return Key.meta('[');
537 }
538 } else {
539 return switch (char3) {
540 'A' => Key.up,
541 'B' => Key.down,
542 'C' => Key.right,
543 'D' => Key.left,
544 'F' => Key.end,
545 'H' => Key.home,
546 else => {
547 std.log.err("Unknown terminal sequence '^[[{c}'", .{char3});
548 try self.key_buffer.append(Key.char(char3));
549 return Key.meta('[');
550 },
551 };
552 }
553 } else if (char2 == 'O') {
554 const char3 = (try readByte(std_in)) orelse { return Key.meta('O'); };
555 return switch (char3) {
556 'F' => Key.end,
557 'H' => Key.home,
558 else => {
559 std.log.err("Unknown terminal sequence '^[O{c}'", .{char3});
560 try self.key_buffer.append(Key.char(char3));
561 return Key.meta('O');
562 },
563 };
564 } else {
565 return Key.meta(char2);
566 }
567}
568// C-<page up> = ^[[5;5~
569// C-<page down> = ^[[6;5
570
571// 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/KeyReader.zig b/src/KeyReader.zig
new file mode 100644
index 0000000..3aac750
--- /dev/null
+++ b/src/KeyReader.zig
@@ -0,0 +1,221 @@
1const es = @import("root");
2const std = @import("std");
3
4const Allocator = std.mem.Allocator;
5const ArrayList = std.ArrayList;
6const File = std.fs.File;
7const Key = es.Key;
8const KeyReader = @This();
9
10pub const Error = Allocator.Error || File.Reader.Error;
11
12allocator: Allocator,
13key_buf: ArrayList(Key),
14
15pub fn init(allocator: Allocator) KeyReader {
16 return .{
17 .allocator = allocator,
18 .key_buf = ArrayList(Key).init(allocator),
19 };
20}
21
22pub fn deinit(self: KeyReader) void {
23 self.key_buf.deinit();
24}
25
26pub fn readKey(self: *KeyReader) Error!Key {
27 if (self.key_buf.items.len > 0) {
28 return self.key_buf.pop();
29 }
30
31 const reader = std.io.getStdIn().reader();
32 const char = try readByteBlocking(reader);
33 if (char == '\x1b') {
34 return self.readMetaKey(reader);
35 } else if (char == '\x9b') {
36 return self.readControlSequence(reader);
37 } else {
38 return Key.char(char);
39 }
40}
41
42fn chooseEscapeKey(final_char: u8) ?Key {
43 return switch (final_char) {
44 'A' => Key.up,
45 'B' => Key.down,
46 'C' => Key.right,
47 'D' => Key.left,
48 'F' => Key.end,
49 'H' => Key.home,
50 else => null,
51 };
52}
53
54fn chooseTildeKey(num: usize) ?Key {
55 return switch (num) {
56 1 => Key.home,
57 2 => Key.insert,
58 3 => Key.delete,
59 4 => Key.end,
60 5 => Key.page_up,
61 6 => Key.page_down,
62 7 => Key.home,
63 8 => Key.end,
64 else => null,
65 };
66}
67
68fn modKey(key: Key, num: usize) ?Key {
69 return switch (num) {
70 2 => Key.shift(key),
71 3 => Key.meta(key),
72 4 => Key.shift(Key.meta(key)),
73 5 => Key.ctrl(key),
74 6 => Key.shift(Key.ctrl(key)),
75 7 => Key.meta(Key.ctrl(key)),
76 8 => Key.shift(Key.meta(Key.ctrl(key))),
77 else => null,
78 };
79}
80
81fn readByte(reader: File.Reader) Error!?u8 {
82 return reader.readByte() catch |err| switch (err) {
83 error.WouldBlock => null,
84 error.EndOfStream => null,
85 else => return @errSetCast(File.Reader.Error, err),
86 };
87}
88
89// TODO: async
90fn readByteBlocking(reader: File.Reader) Error!u8 {
91 var char = try readByte(reader);
92 while (char == null) {
93 std.os.sched_yield() catch {}; // :)
94 char = try readByte(reader);
95 }
96
97 return char.?;
98}
99
100fn readControlSequence(self: *KeyReader, reader: File.Reader) !Key {
101 var parameters = ArrayList(u8).init(self.allocator);
102 defer parameters.deinit();
103
104 var char = try readByte(reader);
105 while (char != null and char.? > 0x30 and char.? < 0x3F) : (char = try readByte(reader)) {
106 try parameters.append(char.?);
107 }
108
109 var intermediates = ArrayList(u8).init(self.allocator);
110 defer intermediates.deinit();
111
112 while (char != null and char.? > 0x20 and char.? < 0x2F) : (char = try readByte(reader)) {
113 try intermediates.append(char.?);
114 }
115
116 if (char) |final| {
117 if (final == '~' and intermediates.items.len == 0) {
118 if (try splitParameters(self.allocator, parameters.items)) |parameter_list| {
119 defer self.allocator.free(parameter_list);
120 if (chooseTildeKey(parameter_list[0])) |key| {
121 if (parameter_list.len == 1) {
122 return key;
123 } else if (parameter_list.len == 2) {
124 if (modKey(key, parameter_list[1])) |mod_key| {
125 return mod_key;
126 }
127 }
128 }
129 }
130 } else if (intermediates.items.len == 0) {
131 if (chooseEscapeKey(final)) |key| {
132 if (try splitParameters(self.allocator, parameters.items)) |parameter_list| {
133 defer self.allocator.free(parameter_list);
134 if (parameter_list.len == 0) {
135 return key;
136 } else if (parameter_list.len == 1) {
137 var count = std.math.max(1, parameter_list[0]) - 1;
138 try self.key_buf.ensureUnusedCapacity(count);
139 while (count > 0) : (count -= 1) {
140 self.key_buf.appendAssumeCapacity(key);
141 }
142
143 return key;
144 } else if (parameter_list.len == 2) {
145 var count = std.math.max(1, parameter_list[0]) - 1;
146 if (modKey(key, parameter_list[1])) |mod_key| {
147 try self.key_buf.ensureUnusedCapacity(count);
148 while (count > 0) : (count -= 1) {
149 self.key_buf.appendAssumeCapacity(mod_key);
150 }
151
152 return mod_key;
153 }
154 }
155 }
156 }
157 }
158
159 std.log.err(
160 "Unknown terminal sequence '^[[{s}|{s}|{c}'",
161 .{parameters.items, intermediates.items, final},
162 );
163
164 try self.key_buf.ensureUnusedCapacity(parameters.items.len + intermediates.items.len + 1);
165 self.key_buf.appendAssumeCapacity(Key.char(final));
166 } else if (parameters.items.len == 0 and intermediates.items.len == 0) {
167 return Key.meta('[');
168 } else {
169 std.log.err(
170 "Unknown terminal sequence '^[[{s}|{s}'",
171 .{parameters.items, intermediates.items},
172 );
173 }
174
175 try self.key_buf.ensureUnusedCapacity(parameters.items.len + intermediates.items.len);
176
177 while (intermediates.items.len > 0) {
178 self.key_buf.appendAssumeCapacity(Key.char(intermediates.pop()));
179 }
180
181 while (parameters.items.len > 0) {
182 self.key_buf.appendAssumeCapacity(Key.char(parameters.pop()));
183 }
184
185 return Key.meta('[');
186}
187
188fn readMetaKey(self: *KeyReader, reader: File.Reader) Error!Key {
189 if (try readByte(reader)) |char| {
190 if (char == '[') {
191 return self.readControlSequence(reader);
192 } else {
193 return Key.meta(char);
194 }
195 } else {
196 return Key.escape;
197 }
198}
199
200fn splitParameters(allocator: Allocator, parameters_string: []const u8) Allocator.Error!?[]usize {
201 var parameters = try ArrayList(usize).initCapacity(allocator, parameters_string.len / 2);
202 defer parameters.deinit();
203
204 var it = std.mem.split(u8, parameters_string, ";");
205 while (it.next()) |parameter_string| {
206 if (parameter_string.len == 0) {
207 // TODO: Default value
208 try parameters.append(1);
209 } else {
210 const parameter = std.fmt.parseUnsigned(
211 usize,
212 parameter_string,
213 10,
214 ) catch { return null; };
215
216 try parameters.append(parameter);
217 }
218 }
219
220 return parameters.toOwnedSlice();
221}
diff --git a/src/key.zig b/src/key.zig
index 5376017..d508ddf 100644
--- a/src/key.zig
+++ b/src/key.zig
@@ -1,34 +1,27 @@
1const std = @import("std"); 1const std = @import("std");
2 2
3pub const Key = enum(u16) { 3pub const Key = enum(u16) {
4 tab = 0x09,
4 return_ = 0x0d, 5 return_ = 0x0d,
5 escape = 0x1b, 6 escape = 0x1b,
6 space = 0x20, 7 space = 0x20,
7 backspace = 0x7f, 8 backspace = 0x7f,
8 max_char = 0xff, 9 max_char = 0xff,
9 10
10 meta_nil = 0x100,
11 meta_max_char = 0x1ff,
12
13 left,
14 right,
15 up, 11 up,
16 down, 12 down,
17 home, 13 right,
14 left,
18 end, 15 end,
16 home,
17 insert,
19 delete, 18 delete,
20 page_up, 19 page_up,
21 page_down, 20 page_down,
22 21
23 ctrl_left, 22 mod_shft = 0x1000,
24 ctrl_right, 23 mod_meta = 0x2000,
25 ctrl_up, 24 mod_ctrl = 0x4000,
26 ctrl_down,
27 ctrl_home,
28 ctrl_end,
29 // ctrl_delete,
30 // ctrl_page_up,
31 // ctrl_page_down,
32 25
33 _, 26 _,
34 27
@@ -36,8 +29,59 @@ pub const Key = enum(u16) {
36 return @intToEnum(Key, ch); 29 return @intToEnum(Key, ch);
37 } 30 }
38 31
39 pub fn ctrl(ch: u8) Key { 32 pub fn shift(k: anytype) Key {
40 return @intToEnum(Key, ch & 0x1f); 33 return Key.mod(.mod_shft, Key.ify(k));
34 }
35
36 pub fn meta(k: anytype) Key {
37 return Key.mod(.mod_meta, Key.ify(k));
38 }
39
40 pub fn ctrl(k: anytype) Key {
41 return Key.mod(.mod_ctrl, Key.ify(k));
42 }
43
44 /// Key.ify == Keyify :)
45 fn ify(k: anytype) Key {
46 return switch (@TypeOf(k)) {
47 comptime_int, u8 => Key.char(k),
48 Key => k,
49 else => unreachable,
50 };
51 }
52
53 fn mod(comptime modifier: Key, k: Key) Key {
54 comptime std.debug.assert(
55 modifier == .mod_shft
56 or modifier == .mod_meta
57 or modifier == .mod_ctrl
58 );
59
60 const shft_int = @enumToInt(Key.mod_shft);
61 const meta_int = @enumToInt(Key.mod_meta);
62 const ctrl_int = @enumToInt(Key.mod_ctrl);
63
64 const max_char_int = @enumToInt(Key.max_char);
65
66 const mod_int = @enumToInt(modifier);
67 const k_int = @enumToInt(k);
68 if (k_int & mod_int == mod_int) {
69 return k;
70 }
71
72 const k_origmod = k_int & (shft_int | meta_int | ctrl_int);
73 const k_nomod = k_int & ~k_origmod;
74 if (k_nomod <= max_char_int) {
75 // Appending S- to a character is not smart
76 std.debug.assert(modifier != .mod_shft);
77 return switch (modifier) {
78 .mod_meta => @intToEnum(Key, k_int | meta_int),
79 .mod_ctrl => @intToEnum(Key, k_origmod | (k_nomod & 0x1f)),
80 else => unreachable,
81 };
82 } else {
83 return @intToEnum(Key, k_int | mod_int);
84 }
41 } 85 }
42 86
43 pub fn format( 87 pub fn format(
@@ -51,70 +95,80 @@ pub const Key = enum(u16) {
51 }; 95 };
52 96
53 return switch (key) { 97 return switch (key) {
98 .tab => std.fmt.formatBuf("<tab>", options, writer),
54 .return_ => std.fmt.formatBuf("<return>", options, writer), 99 .return_ => std.fmt.formatBuf("<return>", options, writer),
55 .escape => std.fmt.formatBuf("<escape>", options, writer), 100 .escape => std.fmt.formatBuf("<escape>", options, writer),
56 .space => std.fmt.formatBuf("<space>", options, writer), 101 .space => std.fmt.formatBuf("<space>", options, writer),
57 .backspace => std.fmt.formatBuf("<backspace>", options, writer), 102 .backspace => std.fmt.formatBuf("<backspace>", options, writer),
58 .max_char => key.formatGeneric(options, writer), 103 .max_char => key.formatGeneric(options, writer),
59 104
60 .meta_nil, .meta_max_char => key.formatGeneric(options, writer),
61
62 .left => std.fmt.formatBuf("<left>", options, writer),
63 .right => std.fmt.formatBuf("<right>", options, writer),
64 .up => std.fmt.formatBuf("<up>", options, writer), 105 .up => std.fmt.formatBuf("<up>", options, writer),
65 .down => std.fmt.formatBuf("<down>", options, writer), 106 .down => std.fmt.formatBuf("<down>", options, writer),
66 .home => std.fmt.formatBuf("<home>", options, writer), 107 .right => std.fmt.formatBuf("<right>", options, writer),
108 .left => std.fmt.formatBuf("<left>", options, writer),
67 .end => std.fmt.formatBuf("<end>", options, writer), 109 .end => std.fmt.formatBuf("<end>", options, writer),
110 .home => std.fmt.formatBuf("<home>", options, writer),
111 .insert => std.fmt.formatBuf("<insert>", options, writer),
68 .delete => std.fmt.formatBuf("<delete>", options, writer), 112 .delete => std.fmt.formatBuf("<delete>", options, writer),
69 .page_up => std.fmt.formatBuf("<page-up>", options, writer), 113 .page_up => std.fmt.formatBuf("<page-up>", options, writer),
70 .page_down => std.fmt.formatBuf("<page-down>", options, writer), 114 .page_down => std.fmt.formatBuf("<page-down>", options, writer),
71 115
72 .ctrl_left => std.fmt.formatBuf("C-<left>", options, writer), 116 .mod_shft, .mod_meta, .mod_ctrl => key.formatGeneric(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
82 _ => key.formatGeneric(options, writer), 117 _ => key.formatGeneric(options, writer),
83 }; 118 };
84 } 119 }
85 120
86 pub fn meta(ch: u8) Key {
87 return @intToEnum(Key, ch + @enumToInt(Key.meta_nil));
88 }
89
90 pub fn metaCtrl(ch: u8) Key {
91 return @intToEnum(Key, (ch & 0x1f) + @enumToInt(Key.meta_nil));
92 }
93
94 fn formatGeneric( 121 fn formatGeneric(
95 key: Key, 122 key: Key,
96 options: std.fmt.FormatOptions, 123 options: std.fmt.FormatOptions,
97 writer: anytype, 124 writer: anytype,
98 ) @TypeOf(writer).Error!void { 125 ) @TypeOf(writer).Error!void {
126 const shft_int = @enumToInt(Key.mod_shft);
127 const meta_int = @enumToInt(Key.mod_meta);
128 const ctrl_int = @enumToInt(Key.mod_ctrl);
129
99 const key_int = @enumToInt(key); 130 const key_int = @enumToInt(key);
100 if (key_int < @enumToInt(Key.space)) { 131 if (key_int & shft_int == shft_int) {
101 const ch = std.ascii.toLower(@intCast(u8, key_int + 0x40)); 132 try std.fmt.formatBuf("S-", options, writer);
102 const buf = [_]u8{ 'C', '-', ch }; 133 return Key.format(
103 return std.fmt.formatBuf(&buf, options, writer); 134 @intToEnum(Key, key_int & ~shft_int),
104 } else if (key_int < @enumToInt(Key.meta_nil)) { 135 "",
105 const buf = [_]u8{@intCast(u8, key_int)}; 136 options,
106 // This should be printed as C-? or <backspace>, it's dealt with in 137 writer,
107 // format() 138 );
108 std.debug.assert(buf[0] != '\x7F'); 139 } else if (key_int & meta_int == meta_int) {
109 return std.fmt.formatBuf(&buf, options, writer);
110 } else if (key_int <= @enumToInt(Key.meta_max_char)) {
111 try std.fmt.formatBuf("M-", options, writer); 140 try std.fmt.formatBuf("M-", options, writer);
112 return Key.format( 141 return Key.format(
113 @intToEnum(Key, key_int - @enumToInt(Key.meta_nil)), 142 @intToEnum(Key, key_int & ~meta_int),
143 "",
144 options,
145 writer,
146 );
147 } else if (key_int & ctrl_int == ctrl_int) {
148 try std.fmt.formatBuf("C-", options, writer);
149 return Key.format(
150 @intToEnum(Key, key_int & ~ctrl_int),
114 "", 151 "",
115 options, 152 options,
116 writer, 153 writer,
117 ); 154 );
155 } else if (key_int < 0x20) {
156 try std.fmt.formatBuf("C-", options, writer);
157 return Key.format(
158 Key.char(@intCast(u8, key_int + 0x40)),
159 "",
160 options,
161 writer
162 );
163 } else if (key_int < 0x100) {
164 const ch = @intCast(u8, key_int);
165 if (std.ascii.isGraph(ch)) {
166 return writer.writeByte(ch);
167 } else {
168 try writer.writeAll("<\\x");
169 try std.fmt.formatIntValue(ch, "X", options, writer);
170 return writer.writeAll(">");
171 }
118 } else { 172 } else {
119 unreachable; 173 unreachable;
120 } 174 }
diff --git a/src/key_state.zig b/src/key_state.zig
index ce20071..2cb9988 100644
--- a/src/key_state.zig
+++ b/src/key_state.zig
@@ -19,7 +19,7 @@ pub const Error = error{
19 std.os.RealPathError; 19 std.os.RealPathError;
20pub const KeyState = fn (*Editor, *Buffer, Key) Error!void; 20pub const KeyState = fn (*Editor, *Buffer, Key) Error!void;
21 21
22pub fn mgState(editor: *Editor, buf: *Buffer, key: Key) Error!void { 22fn mgState(editor: *Editor, buf: *Buffer, key: Key) Error!void {
23 editor.current_state = defaultState; 23 editor.current_state = defaultState;
24 editor.clearStatusMessage(); 24 editor.clearStatusMessage();
25 25
@@ -34,7 +34,23 @@ pub fn mgState(editor: *Editor, buf: *Buffer, key: Key) Error!void {
34 } 34 }
35} 35}
36 36
37pub fn cxState(editor: *Editor, buf: *Buffer, key: Key) Error!void { 37fn mOState(editor: *Editor, buf: *Buffer, key: Key) Error!void {
38 editor.current_state = defaultState;
39 editor.clearStatusMessage();
40
41 switch (key) {
42 // ========== <*> ==========
43 Key.char('F') => buf.moveEndOfLine(),
44 Key.char('H') => buf.moveBeginningOfLine(),
45
46 else => {
47 std.log.debug("Unknown chord: M-O {}", .{key});
48 try editor.setStatusMessage("Unknown chord: M-O {}", .{key});
49 },
50 }
51}
52
53fn cxState(editor: *Editor, buf: *Buffer, key: Key) Error!void {
38 editor.current_state = defaultState; 54 editor.current_state = defaultState;
39 editor.clearStatusMessage(); 55 editor.clearStatusMessage();
40 56
@@ -59,13 +75,14 @@ pub fn cxState(editor: *Editor, buf: *Buffer, key: Key) Error!void {
59pub fn defaultState(editor: *Editor, buf: *Buffer, key: Key) Error!void { 75pub fn defaultState(editor: *Editor, buf: *Buffer, key: Key) Error!void {
60 switch (key) { 76 switch (key) {
61 // ========== M-C-<*> ========== 77 // ========== M-C-<*> ==========
62 Key.metaCtrl('d'), Key.backspace => try buf.backwardDeleteChar(), 78 Key.meta(Key.ctrl('d')), Key.backspace => try buf.backwardDeleteChar(),
63 79
64 // ========== M-<*> ========== 80 // ========== M-<*> ==========
65 Key.meta('g') => { 81 Key.meta('g') => {
66 editor.current_state = mgState; 82 editor.current_state = mgState;
67 try editor.setStatusMessage("M-g-", .{}); 83 try editor.setStatusMessage("M-g-", .{});
68 }, 84 },
85 Key.meta('O') => editor.current_state = mOState,
69 Key.meta('v'), Key.page_up => buf.pageUp(editor.screenrows), 86 Key.meta('v'), Key.page_up => buf.pageUp(editor.screenrows),
70 87
71 // ========== C-<*> ========== 88 // ========== C-<*> ==========
diff --git a/src/main.zig b/src/main.zig
index eae3b8b..176aadd 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -4,8 +4,9 @@ pub const Config = @import("Config.zig");
4pub const Editor = @import("Editor.zig"); 4pub const Editor = @import("Editor.zig");
5pub const files = @import("files.zig"); 5pub const files = @import("files.zig");
6pub const Highlight = @import("highlight.zig").Highlight; 6pub const Highlight = @import("highlight.zig").Highlight;
7pub const key_state = @import("key_state.zig");
8pub const Key = @import("key.zig").Key; 7pub const Key = @import("key.zig").Key;
8pub const KeyReader = @import("KeyReader.zig");
9pub const key_state = @import("key_state.zig");
9pub const RawMode = @import("RawMode.zig"); 10pub const RawMode = @import("RawMode.zig");
10pub const Row = @import("Row.zig"); 11pub const Row = @import("Row.zig");
11pub const search = @import("search.zig").search; 12pub const search = @import("search.zig").search;