zellzig/src/types.zig

609 lines
13 KiB
Zig

const std = @import("std");
const getty = @import("getty");
const json = @import("json");
/// getty deserialization blocks required to properly deserialize messages
pub const dbs = .{
@import("deserialization_blocks/bytearray.zig"),
@import("deserialization_blocks/char.zig"),
@import("deserialization_blocks/char_or_arrow.zig"),
@import("deserialization_blocks/line_and_column.zig"),
};
/// This is a wrapper object around some data deserialized from JSON.
/// Its purpose is to manage the allocation of the data, as the data
/// created by the JSON deserializer needs to be managed using an arena allocator.
///
/// This object must be freed by calling `deinit`.
/// The data can be accessed using the `data` field.
pub fn OwnedDeserData(comptime T: type) type {
return struct {
data: T,
arena: std.heap.ArenaAllocator,
const Self = @This();
pub fn deserialize(alloc: std.mem.Allocator, data: []const u8) !Self {
@setEvalBranchQuota(10000);
var arena = std.heap.ArenaAllocator.init(alloc);
errdefer arena.deinit();
const arena_alloc = arena.allocator();
var deser = json.Deserializer(dbs).withAllocator(arena_alloc, data);
const deser_data = try getty.deserialize(arena_alloc, T, deser.deserializer());
return Self{
.data = deser_data,
.arena = arena,
};
}
pub fn deinit(self: *Self) void {
self.arena.deinit();
}
};
}
/// A struct that represents a single char. It is different from `u8`, as it is
/// deserialized as a 1-letter string, instead of an integer.
pub const Char = struct {
c: u8,
};
test "deserialize Char" {
const json_src =
\\"x"
;
var deser = try OwnedDeserData(Char).deserialize(std.testing.allocator, json_src);
defer deser.deinit();
}
/// A struct that wraps a bytearray.
/// This struct has some custom deserialization behaviour,
/// so that bytearrays aren't deserialized as strings.
pub const Bytearray = struct {
a: []u8,
};
test "deserialize Bytearray" {
const valid_json_src =
\\[
\\ 1,
\\ 2,
\\ 3,
\\ 4
\\]
;
var valid_deser = try OwnedDeserData(Bytearray)
.deserialize(std.testing.allocator, valid_json_src);
defer valid_deser.deinit();
const invalid_json_src =
\\"I'm a string!"
;
try std.testing.expectError(
error.InvalidType,
OwnedDeserData(Bytearray).deserialize(std.testing.allocator, invalid_json_src),
);
}
pub const EventType = enum {
ModeUpdate,
TabUpdate,
Key,
Mouse,
Timer,
CopyToClipboard,
SystemClipboardFailure,
InputReceived,
Visible,
};
pub const Event = union(EventType) {
ModeUpdate: ModeInfo,
TabUpdate: []TabInfo,
Key: Key,
Mouse: Mouse,
Timer: f64,
CopyToClipboard: CopyDestination,
SystemClipboardFailure,
InputReceived,
Visible: bool,
};
test "deserialize Event" {
const json_src =
\\{
\\ "ModeUpdate": {
\\ "mode": "Normal",
\\ "keybinds": [],
\\ "style": {
\\ "colors": {
\\ "source": "Default",
\\ "theme_hue": "Dark",
\\ "fg": {
\\ "EightBit": 15
\\ },
\\ "bg": {
\\ "Rgb": [
\\ 40,
\\ 42,
\\ 54
\\ ]
\\ },
\\ "black": {
\\ "EightBit": 0
\\ },
\\ "red": {
\\ "EightBit": 1
\\ },
\\ "green": {
\\ "EightBit": 2
\\ },
\\ "yellow": {
\\ "EightBit": 3
\\ },
\\ "blue": {
\\ "EightBit": 6
\\ },
\\ "magenta": {
\\ "EightBit": 5
\\ },
\\ "cyan": {
\\ "EightBit": 14
\\ },
\\ "white": {
\\ "EightBit": 15
\\ },
\\ "orange": {
\\ "EightBit": 3
\\ },
\\ "gray": {
\\ "EightBit": 0
\\ },
\\ "purple": {
\\ "EightBit": 0
\\ },
\\ "gold": {
\\ "EightBit": 0
\\ },
\\ "silver": {
\\ "EightBit": 0
\\ },
\\ "pink": {
\\ "EightBit": 0
\\ },
\\ "brown": {
\\ "EightBit": 0
\\ }
\\ },
\\ "rounded_corners": false
\\ },
\\ "capabilities": {
\\ "arrow_fonts": false
\\ },
\\ "session_name": "wacky-bit"
\\ }
\\}
;
var deser = try OwnedDeserData(Event).deserialize(std.testing.allocator, json_src);
defer deser.deinit();
}
pub const Keybind = std.meta.Tuple(&.{ Key, []Action });
pub const KeybindSet = std.meta.Tuple(&.{ InputMode, []Keybind });
test "deserialize KeybindSet" {
const json_src =
\\[
\\ "Normal",
\\ [
\\ [
\\ {
\\ "Alt": "+"
\\ },
\\ [
\\ {
\\ "Resize": "Increase"
\\ }
\\ ]
\\ ]
\\ ]
\\]
;
var deser = try OwnedDeserData(KeybindSet).deserialize(std.testing.allocator, json_src);
defer deser.deinit();
}
pub const ModeInfo = struct {
mode: InputMode,
keybinds: []KeybindSet,
style: Style,
capabilities: PluginCapabilities,
session_name: ?[]u8,
};
pub const InputMode = enum {
Normal,
Locked,
Resize,
Pane,
Tab,
Scroll,
EnterSearch,
Search,
RenameTab,
RenamePane,
Session,
Move,
Prompt,
Tmux,
};
pub const Style = struct {
colors: Palette,
rounded_corners: bool,
};
pub const Palette = struct {
source: PaletteSource,
theme_hue: ThemeHue,
fg: PaletteColor,
bg: PaletteColor,
black: PaletteColor,
red: PaletteColor,
green: PaletteColor,
yellow: PaletteColor,
blue: PaletteColor,
magenta: PaletteColor,
cyan: PaletteColor,
white: PaletteColor,
orange: PaletteColor,
gray: PaletteColor,
purple: PaletteColor,
gold: PaletteColor,
silver: PaletteColor,
pink: PaletteColor,
brown: PaletteColor,
};
test "deserialize Palette" {
const json_src =
\\ {
\\ "source": "Default",
\\ "theme_hue": "Dark",
\\ "fg": {
\\ "EightBit": 15
\\ },
\\ "bg": {
\\ "Rgb": [
\\ 40,
\\ 42,
\\ 54
\\ ]
\\ },
\\ "black": {
\\ "EightBit": 0
\\ },
\\ "red": {
\\ "EightBit": 1
\\ },
\\ "green": {
\\ "EightBit": 2
\\ },
\\ "yellow": {
\\ "EightBit": 3
\\ },
\\ "blue": {
\\ "EightBit": 6
\\ },
\\ "magenta": {
\\ "EightBit": 5
\\ },
\\ "cyan": {
\\ "EightBit": 14
\\ },
\\ "white": {
\\ "EightBit": 15
\\ },
\\ "orange": {
\\ "EightBit": 3
\\ },
\\ "gray": {
\\ "EightBit": 0
\\ },
\\ "purple": {
\\ "EightBit": 0
\\ },
\\ "gold": {
\\ "EightBit": 0
\\ },
\\ "silver": {
\\ "EightBit": 0
\\ },
\\ "pink": {
\\ "EightBit": 0
\\ },
\\ "brown": {
\\ "EightBit": 0
\\ }
\\ }
;
var deser = try OwnedDeserData(Palette).deserialize(std.testing.allocator, json_src);
defer deser.deinit();
}
pub const PaletteSource = enum {
Default,
Xresources,
};
pub const PaletteColor = union(enum) {
Rgb: [3]u8,
EightBit: u8,
};
pub const TabInfo = struct {
position: usize,
name: []u8,
active: bool,
panes_to_hide: usize,
is_fullscreen_active: bool,
is_sync_panes_active: bool,
are_floating_panes_visible: bool,
other_focused_clients: []u16,
};
pub const Key = union(enum) {
PageDown,
PageUp,
Left,
Down,
Up,
Right,
Home,
End,
Backspace,
Delete,
Insert,
F: u8,
Char: Char,
Alt: CharOrArrow,
Ctrl: Char,
BackTab,
Null,
Esc,
};
test "deserialize Key" {
const json_src =
\\[
\\ {
\\ "Alt": "+"
\\ },
\\ "Esc",
\\ {
\\ "Alt": "Left"
\\ },
\\ {
\\ "F": 11
\\ },
\\ {
\\ "Ctrl": "\n"
\\ },
\\ {
\\ "Char": "x"
\\ }
\\]
;
var deser = try OwnedDeserData([]Key).deserialize(std.testing.allocator, json_src);
defer deser.deinit();
}
pub const CharOrArrow = union(enum) {
Char: Char,
Direction: Direction,
};
test "deserialize CharOrArrow" {
var data_1 = try OwnedDeserData(CharOrArrow).deserialize(
std.testing.allocator,
"\"A\"",
);
defer data_1.deinit();
var data_2 = try OwnedDeserData(CharOrArrow).deserialize(
std.testing.allocator,
"\"Left\"",
);
defer data_2.deinit();
try std.testing.expectEqual(CharOrArrow{ .Char = .{ .c = 'A' } }, data_1.data);
try std.testing.expectEqual(CharOrArrow{ .Direction = .Left }, data_2.data);
}
pub const Direction = enum {
Left,
Right,
Up,
Down,
};
pub const Mouse = union(enum) {
ScrollUp: usize,
ScrollDown: usize,
LeftClick: LineAndColumn,
RightClick: LineAndColumn,
Hold: LineAndColumn,
Release: LineAndColumn,
};
// TODO: Make this deserialize both as array and struct, and then remove `Position`.
pub const LineAndColumn = struct {
line: isize,
column: usize,
};
test "deserialize LineAndColumn" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const alloc = arena.allocator();
try std.testing.expectEqual(
LineAndColumn{ .line = 42, .column = 123 },
try json.fromSliceWith(alloc, LineAndColumn, "[42, 123]", dbs),
);
try std.testing.expectEqual(
LineAndColumn{ .line = -42, .column = 123 },
try json.fromSliceWith(alloc, LineAndColumn, "[-42, 123]", dbs),
);
}
pub const CopyDestination = enum {
Command,
Primary,
System,
};
pub const PluginCapabilities = struct {
arrow_fonts: bool,
};
pub const ThemeHue = enum {
Light,
Dark,
};
pub const PluginIds = struct {
plugin_id: u32,
zellij_pid: u32,
};
pub const ResizeDirection = enum {
Left,
Right,
Up,
Down,
Increase,
Decrease,
};
pub const Position = struct {
line: isize,
column: usize,
};
pub const RunPluginFromYaml = struct {
_allow_exec_host_cmd: ?bool,
location: []u8,
};
pub const RunCommand = struct {
command: []u8,
args: ?[][]u8,
cwd: ?[]u8,
};
pub const RunCommandAction = struct {
command: []u8,
args: ?[][]u8,
cwd: ?[]u8,
direction: ?Direction,
};
pub const RunFromYaml = union(enum) {
plugin: RunPluginFromYaml,
command: RunCommand,
};
pub const SplitSize = union(enum) {
Percent: u8,
Fixed: usize,
};
pub const TabLayout = union(enum) {
direction: ?Direction,
pane_name: ?[]u8,
borderless: ?bool,
parts: ?[]TabLayout,
split_size: ?SplitSize,
name: ?[]u8,
focus: ?bool,
run: ?RunFromYaml,
};
pub const SearchDirection = enum {
Up,
Down,
};
pub const SearchOption = enum {
CaseSensitivity,
WholeWord,
Wrap,
};
pub const Action = union(enum) {
Quit,
Write: Bytearray,
WriteChars: []u8,
SwitchToMode: InputMode,
Resize: ResizeDirection,
FocusNextPane,
FocusPreviousPane,
SwitchFocus,
MoveFocus: Direction,
MoveFocusOrTab: Direction,
MovePane: ?Direction,
DumpScreen: []u8,
EditScrollback,
ScrollUp,
ScrollUpAt: Position,
ScrollDown,
ScrollDownAt: Position,
ScrollToBottom,
PageScrollUp,
PageScrollDown,
HalfPageScrollUp,
HalfPageScrollDown,
ToggleFocusFullscreen,
TogglePaneFrames,
ToggleActiveSyncTab,
NewPane: ?Direction,
TogglePaneEmbedOrFloating,
ToggleFloatingPanes,
CloseFocus,
PaneNameInput: Bytearray,
UndoRenamePane,
NewTab: ?TabLayout,
NoOp,
GoToNextTab,
GoToPreviousTab,
CloseTab,
GoToTab: u32,
ToggleTab,
TabNameInput: Bytearray,
UndoRenameTab,
Run: RunCommandAction,
Detach,
LeftClick: Position,
RightClick: Position,
MouseRelease: Position,
MouseHold: Position,
Copy,
Confirm,
Deny,
SkipConfirm: *Action,
SearchInput: Bytearray,
Search: SearchDirection,
SearchToggleOption: SearchOption,
};