diff options
Diffstat (limited to 'src/config.zig')
| -rw-r--r-- | src/config.zig | 292 |
1 files changed, 251 insertions, 41 deletions
diff --git a/src/config.zig b/src/config.zig index 9ad9cc7e..186b29a1 100644 --- a/src/config.zig +++ b/src/config.zig @@ -1,6 +1,7 @@ const std = @import("std"); const builtin = @import("builtin"); const layout = @import("layout.zig"); +const animation = @import("animation.zig"); const limits = @import("memory.zig").limits; const pardes = @import("pardes.zig"); const builtins = @import("builtins.zig"); @@ -129,8 +130,8 @@ pub const leader_path = paths: { } if (builtins.capabilities.scene_shaders) { table.set(.Crt, "tr"); - table.set(.Ripple, "tR"); - table.set(.Glitch, "tg"); + table.set(.Shader, null); + table.set(.ShaderAnimation, null); } if (builtins.EffectCode.enabled) table.set(.EffectCode, null); if (builtins.TreeContext.enabled) table.set(.TreeContext, null); @@ -266,10 +267,15 @@ pub const pipe_selection: []const Chord = &.{.{ .cp = '|' }}; pub const pipe_selection_to: []const Chord = &.{.{ .cp = '|', .alt = true }}; pub const insert_output: []const Chord = &.{.{ .cp = '!' }}; pub const append_output: []const Chord = &.{.{ .cp = '!', .alt = true }}; +pub const keep_pipe: []const Chord = &.{.{ .cp = '$' }}; +pub const macro_record: []const Chord = &.{.{ .cp = 'Q' }}; +pub const macro_replay: []const Chord = &.{.{ .cp = 'q' }}; +pub const repeat_insert: []const Chord = &.{.{ .cp = '.' }}; // Armed inputs are parsed from their distinct, nonempty tag markers. pub const pipe_marker_to = " |-"; pub const pipe_marker_insert = " !"; pub const pipe_marker_append = " !+"; +pub const pipe_marker_keep = " $"; pub const search_submit: []const Chord = &.{.{ .cp = Key.enter }}; // Del from the keyboard on a pane with open panes both above and below it @@ -409,6 +415,8 @@ pub const symbol_marker = " WsSymbols /"; pub const save_marker = " Save "; pub const select_marker = " Select /"; pub const split_marker = " Split /"; +pub const keep_marker = " Keep /"; +pub const remove_marker = " Remove /"; pub const pipe_marker = " |"; pub const search_buffer = "+Search"; @@ -433,6 +441,7 @@ pub const errors_buffer = "+Errors"; // Modal bindings below are checked against Helix by hxdiff and hxparity. pub const goto_prefix = 'g'; pub const view_prefix = 'z'; +pub const view_prefix_sticky = 'Z'; pub const match_prefix = 'm'; pub const replace_prefix = 'r'; pub const next_prefix = ']'; @@ -454,7 +463,7 @@ pub const next_long_word_start: []const Chord = &.{.{ .cp = 'W' }}; pub const prev_long_word_start: []const Chord = &.{.{ .cp = 'B' }}; pub const next_long_word_end: []const Chord = &.{.{ .cp = 'E' }}; pub const line_start: []const Chord = &.{ .{ .cp = '0' }, .{ .cp = Key.home } }; -pub const line_end: []const Chord = &.{ .{ .cp = '$' }, .{ .cp = Key.end } }; +pub const line_end: []const Chord = &.{.{ .cp = Key.end }}; pub const line_first_nonws: []const Chord = &.{.{ .cp = '^' }}; // Bare G does nothing; ge reaches the last line. pub const goto_line: []const Chord = &.{.{ .cp = 'G' }}; @@ -466,6 +475,7 @@ pub const page_up: []const Chord = &.{ .{ .cp = 'b', .ctrl = true }, .{ .cp = Ke pub const view_top: []const Chord = &.{.{ .cp = 't' }}; pub const view_center: []const Chord = &.{ .{ .cp = 'z' }, .{ .cp = 'c' } }; pub const view_bottom: []const Chord = &.{.{ .cp = 'b' }}; +pub const view_middle: []const Chord = &.{.{ .cp = 'm' }}; pub const view_scroll_down: []const Chord = &.{ .{ .cp = 'j' }, .{ .cp = Key.down } }; pub const view_scroll_up: []const Chord = &.{ .{ .cp = 'k' }, .{ .cp = Key.up } }; @@ -480,6 +490,10 @@ pub const goto_column: []const Chord = &.{.{ .cp = '|' }}; pub const goto_view_top: []const Chord = &.{.{ .cp = 't' }}; pub const goto_view_center: []const Chord = &.{.{ .cp = 'c' }}; pub const goto_view_bottom: []const Chord = &.{.{ .cp = 'b' }}; +pub const goto_last_modification: []const Chord = &.{.{ .cp = '.' }}; +pub const goto_word: []const Chord = &.{.{ .cp = 'w' }}; +/// the jump labels' letters, helix's jump_label_alphabet +pub const jump_label_alphabet = "abcdefghijklmnopqrstuvwxyz"; pub const goto_definition: []const Chord = &.{.{ .cp = 'd' }}; pub const goto_declaration: []const Chord = &.{.{ .cp = 'D' }}; pub const goto_type_definition: []const Chord = &.{.{ .cp = 'y' }}; @@ -492,11 +506,19 @@ pub const match_around = 'a'; pub const surround_add = 's'; pub const surround_replace = 'r'; pub const surround_delete = 'd'; +pub const register_prefix = '"'; pub const goto_paragraph: []const Chord = &.{.{ .cp = 'p' }}; pub const add_newline: []const Chord = &.{.{ .cp = ' ' }}; pub const goto_diagnostic: []const Chord = &.{.{ .cp = 'd' }}; pub const goto_diagnostic_end: []const Chord = &.{.{ .cp = 'D' }}; +pub const goto_function: []const Chord = &.{.{ .cp = 'f' }}; +pub const goto_class: []const Chord = &.{.{ .cp = 't' }}; +pub const goto_parameter: []const Chord = &.{.{ .cp = 'a' }}; +pub const goto_comment: []const Chord = &.{.{ .cp = 'c' }}; +pub const goto_test: []const Chord = &.{.{ .cp = 'T' }}; +pub const goto_entry: []const Chord = &.{.{ .cp = 'e' }}; +pub const goto_xml_element: []const Chord = &.{.{ .cp = 'x' }}; pub const insert: []const Chord = &.{.{ .cp = 'i' }}; pub const append: []const Chord = &.{.{ .cp = 'a' }}; @@ -526,6 +548,19 @@ pub const trim_sels: []const Chord = &.{.{ .cp = '_' }}; pub const select_regex: []const Chord = &.{.{ .cp = 's' }}; pub const split_regex: []const Chord = &.{.{ .cp = 'S' }}; +pub const keep_regex: []const Chord = &.{.{ .cp = 'K' }}; +pub const remove_regex: []const Chord = &.{.{ .cp = 'K', .alt = true }}; +pub const ensure_forward: []const Chord = &.{.{ .cp = ':', .alt = true }}; +// helix's tree-sitter selection; Alt-n stays the new terminal, so the next +// sibling is Alt-right alone +pub const expand_node: []const Chord = &.{ .{ .cp = 'o', .alt = true }, .{ .cp = Key.up, .alt = true } }; +pub const shrink_node: []const Chord = &.{ .{ .cp = 'i', .alt = true }, .{ .cp = Key.down, .alt = true } }; +pub const prev_sibling: []const Chord = &.{ .{ .cp = 'p', .alt = true }, .{ .cp = Key.left, .alt = true } }; +pub const next_sibling: []const Chord = &.{.{ .cp = Key.right, .alt = true }}; +pub const all_siblings: []const Chord = &.{.{ .cp = 'a', .alt = true }}; +pub const all_children: []const Chord = &.{.{ .cp = 'I', .alt = true }}; +pub const parent_node_end: []const Chord = &.{.{ .cp = 'e', .alt = true }}; +pub const parent_node_start: []const Chord = &.{.{ .cp = 'b', .alt = true }}; pub const delete: []const Chord = &.{.{ .cp = 'd' }}; pub const delete_noyank: []const Chord = &.{.{ .cp = 'd', .alt = true }}; @@ -538,13 +573,19 @@ pub const switch_case: []const Chord = &.{.{ .cp = '~' }}; pub const to_lowercase: []const Chord = &.{.{ .cp = '`' }}; pub const to_uppercase: []const Chord = &.{.{ .cp = '`', .alt = true }}; pub const join_lines: []const Chord = &.{.{ .cp = 'J' }}; +pub const join_select_space: []const Chord = &.{.{ .cp = 'J', .alt = true }}; +pub const rotate_contents_fwd: []const Chord = &.{.{ .cp = ')', .alt = true }}; +pub const rotate_contents_back: []const Chord = &.{.{ .cp = '(', .alt = true }}; +pub const align_selections: []const Chord = &.{.{ .cp = '&' }}; pub const indent: []const Chord = &.{.{ .cp = '>' }}; pub const unindent: []const Chord = &.{.{ .cp = '<' }}; pub const format: []const Chord = &.{.{ .cp = '=' }}; pub const increment: []const Chord = &.{.{ .cp = 'a', .ctrl = true }}; pub const decrement: []const Chord = &.{.{ .cp = 'x', .ctrl = true }}; -pub const undo: []const Chord = &.{.{ .cp = 'u' }}; -pub const redo: []const Chord = &.{.{ .cp = 'U' }}; +// pardes's history is linear, so helix's earlier/later (Alt-u, Alt-U) are +// undo and redo themselves +pub const undo: []const Chord = &.{ .{ .cp = 'u' }, .{ .cp = 'u', .alt = true } }; +pub const redo: []const Chord = &.{ .{ .cp = 'U' }, .{ .cp = 'U', .alt = true } }; pub const comment_toggle: []const Chord = &.{.{ .cp = 'c', .ctrl = true }}; // Body-normal Esc runs Last; other modes cancel input or leave insert mode. @@ -553,6 +594,8 @@ pub const escape: []const Chord = &.{.{ .cp = Key.escape }}; pub const insert_backspace_alias: []const Chord = &.{.{ .cp = 'h', .ctrl = true }}; pub const insert_enter_alias: []const Chord = &.{.{ .cp = 'j', .ctrl = true }}; pub const insert_delete_alias: []const Chord = &.{.{ .cp = 'd', .ctrl = true }}; +pub const insert_register: []const Chord = &.{.{ .cp = 'r', .ctrl = true }}; +pub const insert_checkpoint: []const Chord = &.{.{ .cp = 's', .ctrl = true }}; pub const delete_word_backward: []const Chord = &.{ .{ .cp = 'w', .ctrl = true }, .{ .cp = Key.backspace, .alt = true } }; pub const delete_word_forward: []const Chord = &.{ .{ .cp = 'd', .alt = true }, .{ .cp = Key.delete, .alt = true } }; pub const kill_to_line_start: []const Chord = &.{.{ .cp = 'u', .ctrl = true }}; @@ -611,10 +654,92 @@ pub const Runtime = struct { tagline_percent: u8 = 100, } = .{}, - panel_transition: layout.Transition = .off, - scene_effects: layout.SceneEffect = .{}, + panel_transition: animation.Transition = .off, + /// The post chain (docs/render-pipeline.md ยง6), in the order it runs. + post: Post = .{}, + /// When the chain redraws on its own, past the frames it post-processes: + /// never, while the window has the focus, or always (ghostty's + /// custom-shader-animation). + shader_animation: ShaderAnimation = .on, - pub fn toggleTransition(state: *Runtime, effect: layout.Transition) void { + pub const ShaderAnimation = enum { off, on, always }; + + /// Each pass the bundled Crt or a Shadertoy file. + pub const Post = struct { + pub const max = 8; + pub const Entry = struct { + scene: ?Scene = null, + path: Text(limits.host_path_cap) = .{}, + /// A bundled pass's strength, 1 to `max_level`; 0 for a file. + level: u8 = 0, + }; + pub const max_level = 3; + /// What `on` and a bare word turn a bundled pass on at. + pub const default_level = 2; + entries: [max]Entry = @splat(.{}), + len: u8 = 0, + /// Bumped on every change: a shell compiles the chain again when it + /// moves. + generation: u32 = 0, + + pub fn list(post: *const Post) []const Entry { + return post.entries[0..post.len]; + } + + pub fn has(post: *const Post, scene: Scene) bool { + return post.level(scene) != 0; + } + + /// A bundled pass's level; 0 when it is not in the chain. + pub fn level(post: *const Post, scene: Scene) u8 { + for (post.list()) |entry| if (entry.scene == scene) return entry.level; + return 0; + } + + /// Sets a bundled pass's level: 0 takes it out, anything else puts it + /// in at the end or changes it where it is. + pub fn setLevel(post: *Post, scene: Scene, to: u8) bool { + std.debug.assert(to <= max_level); + if (to == 0) return post.set(scene, "", false); + for (post.entries[0..post.len]) |*entry| if (entry.scene == scene) { + if (entry.level != to) post.generation +%= 1; + entry.level = to; + return true; + }; + if (!post.set(scene, "", true)) return false; + post.entries[post.len - 1].level = to; + return true; + } + + /// Puts the pass in the chain, at its end, or takes it out; `want` + /// null flips it. False when the chain is full or the path too long. + pub fn set(post: *Post, scene: ?Scene, path: []const u8, want: ?bool) bool { + const at: ?usize = for (post.list(), 0..) |entry, i| { + if (entry.scene == scene and std.mem.eql(u8, entry.path.get(), path)) break i; + } else null; + const on = want orelse (at == null); + if (on == (at != null)) return true; + post.generation +%= 1; + if (at) |i| { + std.mem.copyForwards(Entry, post.entries[i .. post.len - 1], post.entries[i + 1 .. post.len]); + post.len -= 1; + return true; + } + if (post.len == max) return false; + post.entries[post.len] = .{ .scene = scene }; + if (!post.entries[post.len].path.set(path)) return false; + post.len += 1; + return true; + } + + pub fn clear(post: *Post) void { + if (post.len == 0) return; + post.len = 0; + post.generation +%= 1; + } + }; + + pub fn toggleTransition(state: *Runtime, effect: animation.Transition) void { std.debug.assert(effect != .off); state.panel_transition = if (state.panel_transition == effect) .off else effect; } @@ -691,7 +816,8 @@ pub const Runtime = struct { pub const MessageTime = enum { linger, fall, dissolve }; pub const Toggle = enum { colors, wrap, tree_context_tag_style, tag_bottom, debug, focus_tint, column_tags, syntax_bold, verbose, message_animation, ligatures }; - pub const Scene = std.meta.FieldEnum(layout.SceneEffect); + /// The bundled post pass, a Shadertoy file in shaders/post/. + pub const Scene = enum { crt }; pub const Action = union(enum) { toggle: Toggle, @@ -703,8 +829,12 @@ pub const Runtime = struct { window_blur, message_ms: MessageTime, dump_dir, - transition: layout.Transition, + transition: animation.Transition, scene: Scene, + /// A Shadertoy file into the post chain or out of it; `off` empties + /// it of files. + shader, + shader_animation, }; pub const Setting = struct { @@ -755,8 +885,8 @@ pub const Runtime = struct { .{ .word = "PanelScramble", .action = .{ .transition = .scramble }, .availability = .panel_transitions }, .{ .word = "PanelType", .action = .{ .transition = .typewriter }, .availability = .panel_transitions }, .{ .word = "Crt", .action = .{ .scene = .crt }, .availability = .scene_shaders }, - .{ .word = "Ripple", .action = .{ .scene = .ripple }, .availability = .scene_shaders }, - .{ .word = "Glitch", .action = .{ .scene = .glitch }, .availability = .scene_shaders }, + .{ .word = "Shader", .action = .shader, .availability = .scene_shaders }, + .{ .word = "ShaderAnimation", .action = .shader_animation, .availability = .scene_shaders }, }; pub fn find(name: []const u8) ?Setting { @@ -790,7 +920,7 @@ pub const Runtime = struct { @compileError("window blur must use the window-blur capability"), .transition => if (setting.availability != .panel_transitions) @compileError("panel effects must use the panel-transition capability"), - .scene => if (setting.availability != .scene_shaders) + .scene, .shader, .shader_animation => if (setting.availability != .scene_shaders) @compileError("scene effects must use the scene-shader capability"), // A toggle is plain state any shell can hold, except one for // what only some shells draw: that one exists where they do. @@ -808,7 +938,7 @@ pub const Runtime = struct { if (actionCount(.shell) != 1 or actionCount(.theme) != 1 or actionCount(.font) != 1 or actionCount(.tagline_size) != 1 or actionCount(.window_opacity) != 1 or actionCount(.window_blur) != 1) @compileError("Shell, Theme, Font, TaglineSize, WindowOpacity, and WindowBlur actions must each occur exactly once"); - for (std.enums.values(layout.Transition)) |effect| { + for (std.enums.values(animation.Transition)) |effect| { const expected: comptime_int = @intFromBool(effect != .off); if (actionCount(.{ .transition = effect }) != expected) @compileError("non-off panel transition must occur exactly once: " ++ @tagName(effect)); @@ -816,9 +946,9 @@ pub const Runtime = struct { for (std.enums.values(Scene)) |effect| { if (actionCount(.{ .scene = effect }) != 1) @compileError("scene effect must occur exactly once: " ++ @tagName(effect)); - if (@FieldType(layout.SceneEffect, @tagName(effect)) != bool) - @compileError("scene effect fields must be booleans: " ++ @tagName(effect)); } + if (actionCount(.shader) != 1 or actionCount(.shader_animation) != 1) + @compileError("Shader and ShaderAnimation must each occur exactly once"); } pub fn apply(state: *Runtime, setting: Setting, argument: ?[]const u8) bool { @@ -884,13 +1014,36 @@ pub const Runtime = struct { } }, .scene => |effect| { - const want = switchedTo(argument) catch return false; - switch (effect) { - inline else => |field| { - const value = &@field(state.scene_effects, @tagName(field)); - value.* = want orelse !value.*; - }, + // A level, 0 (off) to 3; `on` is the default level, `off` 0, + // and bare flips between the two. + const text = std.mem.trim(u8, argument orelse "", " \t\r\n"); + const to: u8 = if (text.len == 0) + (if (state.post.has(effect)) 0 else Post.default_level) + else if (std.mem.eql(u8, text, "on")) + Post.default_level + else if (std.mem.eql(u8, text, "off")) + 0 + else + std.fmt.parseInt(u8, text, 10) catch return false; + if (to > Post.max_level) return false; + return state.post.setLevel(effect, to); + }, + .shader => { + const path = std.mem.trim(u8, argument orelse return false, " \t\r\n"); + if (path.len == 0) return false; + if (std.mem.eql(u8, path, "off")) { + var i: usize = 0; + while (i < state.post.len) { + const entry = state.post.entries[i]; + if (entry.scene == null) _ = state.post.set(null, entry.path.get(), false) else i += 1; + } + return true; } + return state.post.set(null, path, null); + }, + .shader_animation => { + const text = std.mem.trim(u8, argument orelse return false, " \t\r\n"); + state.shader_animation = std.meta.stringToEnum(ShaderAnimation, text) orelse return false; }, .theme, .font => return false, } @@ -931,7 +1084,7 @@ pub const Runtime = struct { return if (text.len == 0) "(none)" else text; } - fn transitionSettingName(transition: layout.Transition) []const u8 { + fn transitionSettingName(transition: animation.Transition) []const u8 { if (transition == .off) return "off"; return findAction(.{ .transition = transition }).?.word; } @@ -1042,11 +1195,25 @@ pub const Runtime = struct { try out.print("{s} unsupported\n", .{setting.word}); continue; } - const enabled = switch (effect) { - inline else => |field| @field(state.scene_effects, @tagName(field)), + const level = state.post.level(effect); + if (level == 0) try out.print("{s} off\n", .{setting.word}) else try out.print("{s} {d}\n", .{ setting.word, level }); + }, + .shader => { + if (!setting.enabled(context.capabilities)) { + try out.print("{s} unsupported\n", .{setting.word}); + continue; + } + var files: usize = 0; + for (state.post.list()) |entry| if (entry.scene == null) { + try out.print("{s} {s}\n", .{ setting.word, entry.path.get() }); + files += 1; }; - try out.print("{s} {s}\n", .{ setting.word, onOff(enabled) }); + if (files == 0) try out.print("{s} off\n", .{setting.word}); }, + .shader_animation => if (setting.enabled(context.capabilities)) + try out.print("{s} {s}\n", .{ setting.word, @tagName(state.shader_animation) }) + else + try out.print("{s} unsupported\n", .{setting.word}), }; if (context.startup_config_path) |path| @@ -1082,20 +1249,18 @@ pub const Runtime = struct { const slide = findAction(.{ .transition = .slide }).?; const zoom = findAction(.{ .transition = .zoom }).?; try std.testing.expect(apply(&state, slide, "on")); - try std.testing.expectEqual(layout.Transition.slide, state.panel_transition); + try std.testing.expectEqual(animation.Transition.slide, state.panel_transition); try std.testing.expect(apply(&state, zoom, "off")); - try std.testing.expectEqual(layout.Transition.slide, state.panel_transition); + try std.testing.expectEqual(animation.Transition.slide, state.panel_transition); try std.testing.expect(apply(&state, slide, "on")); - try std.testing.expectEqual(layout.Transition.slide, state.panel_transition); + try std.testing.expectEqual(animation.Transition.slide, state.panel_transition); try std.testing.expect(apply(&state, slide, null)); - try std.testing.expectEqual(layout.Transition.off, state.panel_transition); + try std.testing.expectEqual(animation.Transition.off, state.panel_transition); try std.testing.expect(!apply(&state, slide, "yes")); const scene = findAction(.{ .scene = std.enums.values(Scene)[0] }).?; try std.testing.expect(apply(&state, scene, "on")); try std.testing.expect(apply(&state, scene, "on")); - switch (std.enums.values(Scene)[0]) { - inline else => |f| try std.testing.expect(@field(state.scene_effects, @tagName(f))), - } + try std.testing.expect(state.post.has(std.enums.values(Scene)[0])); } test "a toggle flips when bare and sets when told on or off" { @@ -1128,11 +1293,56 @@ pub const Runtime = struct { try std.testing.expectEqualStrings("fish", state.shell.requested.get()); try std.testing.expect(state.shell.pending); try std.testing.expect(apply(&state, find("PanelAscii").?, null)); - try std.testing.expectEqual(layout.Transition.ascii, state.panel_transition); + try std.testing.expectEqual(animation.Transition.ascii, state.panel_transition); try std.testing.expect(apply(&state, find("PanelAscii").?, null)); - try std.testing.expectEqual(layout.Transition.off, state.panel_transition); + try std.testing.expectEqual(animation.Transition.off, state.panel_transition); try std.testing.expect(apply(&state, find("Crt").?, null)); - try std.testing.expect(state.scene_effects.crt); + try std.testing.expect(state.post.has(.crt)); + } + + test "the post chain runs in the order its passes were put in, and Shader off empties it of files" { + var state: Runtime = .{}; + try std.testing.expect(apply(&state, find("Shader").?, " ~/a.glsl\n")); + try std.testing.expect(apply(&state, find("Crt").?, "on")); + try std.testing.expect(apply(&state, find("Shader").?, "/b.glsl")); + const chain = state.post.list(); + try std.testing.expectEqual(@as(usize, 3), chain.len); + try std.testing.expectEqualStrings("~/a.glsl", chain[0].path.get()); + try std.testing.expectEqual(Scene.crt, chain[1].scene.?); + try std.testing.expectEqualStrings("/b.glsl", chain[2].path.get()); + const moved = state.post.generation; + // A file named again leaves; the others keep their order. + try std.testing.expect(apply(&state, find("Shader").?, "~/a.glsl")); + try std.testing.expect(state.post.generation != moved); + try std.testing.expectEqual(Scene.crt, state.post.list()[0].scene.?); + try std.testing.expect(apply(&state, find("Shader").?, "off")); + try std.testing.expectEqual(@as(usize, 1), state.post.len); + try std.testing.expect(!apply(&state, find("Shader").?, null)); + try std.testing.expect(apply(&state, find("ShaderAnimation").?, "always")); + try std.testing.expectEqual(ShaderAnimation.always, state.shader_animation); + try std.testing.expect(!apply(&state, find("ShaderAnimation").?, "sometimes")); + } + + test "a bundled pass takes a level: on is the default, 0 and off take it out, and the old words still flip" { + var state: Runtime = .{}; + const crt = find("Crt").?; + try std.testing.expect(apply(&state, crt, "on")); + try std.testing.expectEqual(@as(u8, Post.default_level), state.post.level(.crt)); + try std.testing.expect(apply(&state, find("Shader").?, "/after.glsl")); + const moved = state.post.generation; + try std.testing.expect(apply(&state, crt, "3")); + try std.testing.expectEqual(@as(u8, 3), state.post.level(.crt)); + // A new level keeps its place in the chain. + try std.testing.expectEqual(Scene.crt, state.post.list()[0].scene.?); + try std.testing.expect(state.post.generation != moved); + try std.testing.expect(!apply(&state, crt, "4")); + try std.testing.expect(!apply(&state, crt, "loud")); + try std.testing.expect(apply(&state, crt, "0")); + try std.testing.expect(!state.post.has(.crt)); + try std.testing.expect(apply(&state, crt, null)); + try std.testing.expectEqual(@as(u8, Post.default_level), state.post.level(.crt)); + try std.testing.expect(apply(&state, crt, "off")); + try std.testing.expectEqual(@as(u8, 0), state.post.level(.crt)); } test "tagline size validates before mutating live state" { @@ -1253,6 +1463,8 @@ pub const Runtime = struct { .window_blur => "40", .message_ms => "1500", .dump_dir => "~/dumps", + .shader => "~/crt.glsl", + .shader_animation => "always", else => null, }; try std.testing.expect(apply(&state, setting, argument)); @@ -1299,10 +1511,10 @@ pub const Runtime = struct { .typewriter => "Panel transition PanelType\n", }, .scene => |effect| switch (effect) { - .crt => "Crt on\n", - .ripple => "Ripple on\n", - .glitch => "Glitch on\n", + .crt => "Crt 2\n", }, + .shader => "Shader ~/crt.glsl\n", + .shader_animation => "ShaderAnimation always\n", .theme, .font => unreachable, }; try std.testing.expect(std.mem.indexOf(u8, report, expected) != null); @@ -1370,8 +1582,6 @@ pub const Runtime = struct { try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Font requested") == null); try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Panel transition off\n") != null); try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Crt unsupported\n") != null); - try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Ripple unsupported\n") != null); - try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Glitch unsupported\n") != null); try std.testing.expect(std.mem.indexOf(u8, defaults_report, "TaglineSize unsupported\n") != null); try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Look hover delay: off\n") != null); try std.testing.expect(std.mem.indexOf(u8, defaults_report, "Native images: off\n") != null); |
