609 lines
13 KiB
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,
|
|
};
|