Zig uncooked terminal library.

docs(zut.zig): Added doc-comments.

+186 -181
+1 -1
build.zig
··· 16 16 const optimize = b.standardOptimizeOption(.{}); 17 17 18 18 const exe = b.addExecutable(.{ 19 - .name = "io", 19 + .name = "zut", 20 20 // In this case the main source file is merely a path, however, in more 21 21 // complicated build scripts, this could be a generated file. 22 22 .root_source_file = .{ .path = "src/main.zig" },
+185 -180
src/zut.zig
··· 1 + //! VERSION: 0.0.2 2 + //! This module provides functionality to the uncooked terminal. 3 + //! The following termios setup was retrieved from zig.news[1] on 2024-08-04 4 + //! 10:57:50. 5 + //! - [1]: https://zig.news/lhp/want-to-create-a-tui-application-the-basics-of-uncooked-terminal-io-17gm 6 + 1 7 const std = @import("std"); 2 8 const os = std.os; 3 9 const fs = std.fs; 4 10 const mem = std.mem; 5 11 6 - const stdout = std.io.getStdOut().writer(); 7 - 8 - // termios setup: https://zig.news/lhp/want-to-create-a-tui-application-the-basics-of-uncooked-terminal-io-17gm 12 + /// Key enum. This is meant to capture any and all ascii input from the user. 13 + /// This can only capture ascii input and some simple escape sequences. 9 14 pub const Key = enum { 10 - NUL, 11 - SOH, 12 - STX, 13 - ETX, 14 - EOT, 15 - ENQ, 16 - ACK, 17 - BEL, 18 - BS, 19 - TAB, 20 - LF, 21 - VT, 22 - FF, 23 - CR, 24 - SO, 25 - SI, 26 - DLE, 27 - DC1, 28 - DC2, 29 - DC3, 30 - DC4, 31 - NAK, 32 - SYN, 33 - ETB, 34 - CAN, 35 - EM, 36 - SUB, 37 - ESC, 38 - FS, 39 - GS, 40 - RS, 41 - US, 15 + Null, 16 + StartOfHeading, 17 + StartOfText, 18 + EndOfText, 19 + EndOfTransmission, 20 + Enquiry, 21 + Acknowledge, 22 + Bell, 23 + Backspace, 24 + Tab, 25 + NewLine, 26 + VerticalTab, 27 + NewPage, 28 + CarriageReturn, 29 + ShiftOut, 30 + ShiftIn, 31 + DataLinkEscape, 32 + DeviceControl1, 33 + DeviceControl2, 34 + DeviceControl3, 35 + DeviceControl4, 36 + NegativeAcknowledgement, 37 + SynchronousIdle, 38 + EndOfTransmissionBlock, 39 + Cancel, 40 + EndOfMedium, 41 + Substitute, 42 + Escape, 43 + FileSeparator, 44 + GroupSeparator, 45 + RecordSeparator, 46 + UnitSeparator, 42 47 Space, 43 - exclamation_point, 44 - double_quote, 45 - octothorpe, 46 - dollar_sign, 47 - percent_sign, 48 - ampersand, 49 - quote, 50 - l_paren, 51 - r_paren, 52 - star, 53 - plus, 54 - comma, 55 - minus, 56 - period, 57 - slash, 58 - zero, 59 - one, 60 - two, 61 - three, 62 - four, 63 - five, 64 - six, 65 - seven, 66 - eight, 67 - nine, 68 - colon, 69 - semicolon, 70 - lt, 71 - eq, 72 - gt, 73 - question_mark, 74 - at, 48 + ExclamationMark, 49 + QuotationMark, 50 + NumberSign, 51 + DollarSign, 52 + PercentSign, 53 + Ampersand, 54 + Apostrophe, 55 + LParen, 56 + RParen, 57 + Asterisk, 58 + PlusSign, 59 + Comma, 60 + MinusSign, 61 + Period, 62 + ForwardSlash, 63 + Zero, 64 + One, 65 + Two, 66 + Three, 67 + Four, 68 + Five, 69 + Six, 70 + Seven, 71 + Eight, 72 + Nine, 73 + Colon, 74 + SemiColon, 75 + LessThan, 76 + Equal, 77 + GreaterThan, 78 + QuestionMark, 79 + At, 75 80 A, 76 81 B, 77 82 C, ··· 98 103 X, 99 104 Y, 100 105 Z, 101 - l_bracket, 102 - backslash, 103 - r_bracket, 104 - exp, 105 - underscore, 106 - backtick, 106 + LBracket, 107 + BackwardSlash, 108 + RBracket, 109 + Caret, 110 + UnderScore, 111 + Backtick, 107 112 a, 108 113 b, 109 114 c, ··· 130 135 x, 131 136 y, 132 137 z, 133 - l_brace, 134 - pipe, 135 - r_brace, 136 - simmetric, 138 + LBrace, 139 + Pipe, 140 + RBrace, 141 + Tilde, 137 142 // 138 - arrow_left, 139 - arrow_right, 140 - arrow_up, 141 - arrow_down, 142 - out_of_bounds, 143 + ArrowLeft, 144 + ArrowRight, 145 + ArrowUp, 146 + ArrowDown, 143 147 }; 144 148 149 + /// Key errors. 150 + const KeyError = error{UnkownKey}; 151 + 152 + /// Waits until the user presses a key and returns the pressed key as the `Key` 153 + /// enum. 145 154 pub fn get_key() !Key { 146 155 var tty: fs.File = try fs.cwd().openFile("/dev/tty", .{ .mode = fs.File.OpenMode.read_write }); 147 156 defer tty.close(); ··· 157 166 raw.cc[os.system.V.TIME] = 0; 158 167 raw.cc[os.system.V.MIN] = 1; 159 168 160 - try os.tcsetattr(tty.handle, .FLUSH, raw); 169 + os.tcsetattr(tty.handle, .FLUSH, raw) catch {}; 161 170 var flushed: bool = true; 171 + defer { 172 + if (flushed) { 173 + os.tcsetattr(tty.handle, .FLUSH, original_termios) catch {}; 174 + } else { 175 + os.tcsetattr(tty.handle, .NOW, original_termios) catch {}; 176 + } 177 + } 162 178 163 - try tty.writeAll("\x1B[s"); // Save cursor position. 164 - try tty.writeAll("\x1B[?25l"); // Hide the cursor. 179 + tty.writeAll("\x1B[s") catch {}; // Save cursor position. 180 + tty.writeAll("\x1B[?25l") catch {}; // Hide the cursor. 165 181 defer tty.writeAll("\x1B[u") catch {}; // Restore cursor position. 166 182 167 - // try tty.writeAll("\x1B[?47h"); // Save screen. 168 - // defer tty.writeAll("\x1B[?47l") catch {}; // Restore screen. 169 - 170 - // try tty.writeAll("\x1B[?1049h"); // Enable alternative buffer. 171 - // defer tty.writeAll("\x1B[?1049l") catch {}; // Disable alternative buffer. 172 - 173 183 var buffer: [1]u8 = undefined; 174 - _ = try tty.read(&buffer); 184 + _ = tty.read(&buffer) catch 0; 175 185 176 186 const ret = switch (buffer[0]) { 177 - 0 => .NUL, 178 - 1 => .SOH, 179 - 2 => .STX, 180 - 3 => .ETX, 181 - 4 => .EOT, 182 - 5 => .ENQ, 183 - 6 => .ACK, 184 - 7 => .BEL, 185 - 8 => .BS, 186 - 9 => .TAB, 187 - 10 => .LF, 188 - 11 => .VT, 189 - 12 => .FF, 190 - 13 => .CR, 191 - 14 => .SO, 192 - 15 => .SI, 193 - 16 => .DLE, 194 - 17 => .DC1, 195 - 18 => .DC2, 196 - 19 => .DC3, 197 - 20 => .DC4, 198 - 21 => .NAK, 199 - 22 => .SYN, 200 - 23 => .ETB, 201 - 24 => .CAN, 202 - 25 => .EM, 203 - 26 => .SUB, 187 + 0 => .Null, 188 + 1 => .StartOfHeading, 189 + 2 => .StartOfText, 190 + 3 => .EndOfText, 191 + 4 => .EndOfTransmission, 192 + 5 => .Enquiry, 193 + 6 => .Acknowledge, 194 + 7 => .Bell, 195 + 8 => .Backspace, 196 + 9 => .Tab, 197 + 10 => .NewLine, 198 + 11 => .VerticalTab, 199 + 12 => .NewPage, 200 + 13 => .CarriageReturn, 201 + 14 => .ShiftOut, 202 + 15 => .ShiftIn, 203 + 16 => .DataLinkEscape, 204 + 17 => .DeviceControl1, 205 + 18 => .DeviceControl2, 206 + 19 => .DeviceControl3, 207 + 20 => .DeviceControl4, 208 + 21 => .NegativeAcknowledgement, 209 + 22 => .SynchronousIdle, 210 + 23 => .EndOfTransmissionBlock, 211 + 24 => .Cancel, 212 + 25 => .EndOfMedium, 213 + 26 => .Substitute, 204 214 27 => esc: { 205 215 raw.cc[os.system.V.TIME] = 1; 206 216 raw.cc[os.system.V.MIN] = 0; 207 217 208 218 flushed = false; 209 - try os.tcsetattr(tty.handle, .NOW, raw); 219 + os.tcsetattr(tty.handle, .NOW, raw) catch {}; 210 220 211 221 var esc_buffer: [8]u8 = undefined; 212 222 const esc_read = try tty.read(&esc_buffer); ··· 215 225 raw.cc[os.system.V.MIN] = 1; 216 226 217 227 if (esc_read == 0) { 218 - break :esc Key.ESC; 228 + break :esc Key.Escape; 219 229 } else if (mem.eql(u8, esc_buffer[0..esc_read], "[A")) { 220 - break :esc Key.arrow_up; 230 + break :esc Key.ArrowUp; 221 231 } else if (mem.eql(u8, esc_buffer[0..esc_read], "[B")) { 222 - break :esc Key.arrow_down; 232 + break :esc Key.ArrowDown; 223 233 } else if (mem.eql(u8, esc_buffer[0..esc_read], "[D")) { 224 - break :esc Key.arrow_left; 234 + break :esc Key.ArrowLeft; 225 235 } else if (mem.eql(u8, esc_buffer[0..esc_read], "[C")) { 226 - break :esc Key.arrow_right; 236 + break :esc Key.ArrowRight; 227 237 } else { 228 - try stdout.print("{s}\n", .{esc_buffer[0..esc_read]}); 229 - break :esc Key.NUL; 238 + return KeyError.UnkownKey; 230 239 } 231 240 }, 232 - 28 => .FS, 233 - 29 => .GS, 234 - 30 => .RS, 235 - 31 => .US, 241 + 28 => .FileSeparator, 242 + 29 => .GroupSeparator, 243 + 30 => .RecordSeparator, 244 + 31 => .UnitSeparator, 236 245 32 => .Space, 237 - 33 => .exclamation_point, 238 - 34 => .double_quote, 239 - 35 => .octothorpe, 240 - 36 => .dollar_sign, 241 - 37 => .percent_sign, 242 - 38 => .ampersand, 243 - 39 => .quote, 244 - 40 => .l_paren, 245 - 41 => .r_paren, 246 - 42 => .star, 247 - 43 => .plus, 248 - 44 => .comma, 249 - 45 => .minus, 250 - 46 => .period, 251 - 47 => .slash, 252 - 48 => .zero, 253 - 49 => .one, 254 - 50 => .two, 255 - 51 => .three, 256 - 52 => .four, 257 - 53 => .five, 258 - 54 => .six, 259 - 55 => .seven, 260 - 56 => .eight, 261 - 57 => .nine, 262 - 58 => .colon, 263 - 59 => .semicolon, 264 - 60 => .lt, 265 - 61 => .eq, 266 - 62 => .gt, 267 - 63 => .question_mark, 268 - 64 => .at, 246 + 33 => .ExclamationMark, 247 + 34 => .QuotationMark, 248 + 35 => .NumberSign, 249 + 36 => .DollarSign, 250 + 37 => .PercentSign, 251 + 38 => .Ampersand, 252 + 39 => .Apostrophe, 253 + 40 => .LParen, 254 + 41 => .RParen, 255 + 42 => .Asterisk, 256 + 43 => .PlusSign, 257 + 44 => .Comma, 258 + 45 => .MinusSign, 259 + 46 => .Period, 260 + 47 => .ForwardSlash, 261 + 48 => .Zero, 262 + 49 => .One, 263 + 50 => .Two, 264 + 51 => .Three, 265 + 52 => .Four, 266 + 53 => .Five, 267 + 54 => .Six, 268 + 55 => .Seven, 269 + 56 => .Eight, 270 + 57 => .Nine, 271 + 58 => .Colon, 272 + 59 => .SemiColon, 273 + 60 => .LessThan, 274 + 61 => .Equal, 275 + 62 => .GreaterThan, 276 + 63 => .QuestionMark, 277 + 64 => .At, 269 278 65 => .A, 270 279 66 => .B, 271 280 67 => .C, ··· 292 301 88 => .X, 293 302 89 => .Y, 294 303 90 => .Z, 295 - 91 => .l_bracket, 296 - 92 => .backslash, 297 - 93 => .r_bracket, 298 - 94 => .exp, 299 - 95 => .underscore, 300 - 96 => .backtick, 304 + 91 => .LBracket, 305 + 92 => .BackwardSlash, 306 + 93 => .RBracket, 307 + 94 => .Caret, 308 + 95 => .UnderScore, 309 + 96 => .Backtick, 301 310 97 => .a, 302 311 98 => .b, 303 312 99 => .c, ··· 324 333 120 => .x, 325 334 121 => .y, 326 335 122 => .z, 327 - 123 => .l_brace, 328 - 124 => .pipe, 329 - 125 => .r_brace, 330 - 126 => .simmetric, 331 - else => .out_of_bounds, 336 + 123 => .LBrace, 337 + 124 => .Pipe, 338 + 125 => .RBrace, 339 + 126 => .Tilde, 340 + else => { 341 + return KeyError.UnkownKey; 342 + }, 332 343 }; 333 - 334 - if (flushed) { 335 - try os.tcsetattr(tty.handle, .FLUSH, original_termios); 336 - } else { 337 - try os.tcsetattr(tty.handle, .NOW, original_termios); 338 - } 339 344 340 345 return ret; 341 346 }