Initial Commit!
This commit is contained in:
commit
105eb205e6
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# Zig
|
||||||
|
zig-*
|
||||||
|
|
||||||
|
# Packages
|
||||||
|
deps.zig
|
||||||
|
|
||||||
|
# Gyro
|
||||||
|
.gyro/
|
||||||
|
gyro.lock
|
33
README.md
Normal file
33
README.md
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# zellzig
|
||||||
|
A zig framework for writing [zellij](https://zellij.dev/) plugins.
|
||||||
|
|
||||||
|
# usage
|
||||||
|
For an example of how to use it, see the example directory.
|
||||||
|
|
||||||
|
Here's a quick overview:
|
||||||
|
```zig
|
||||||
|
const std = @import("std");
|
||||||
|
const zz = @import("zellzig");
|
||||||
|
|
||||||
|
comptime {
|
||||||
|
// register plugin
|
||||||
|
zz.createPlugin(@This());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init() void {
|
||||||
|
// do initialization stuff
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(ev: zz.Event) void {
|
||||||
|
// handle events
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(rows: i32, cols: i32) void {
|
||||||
|
// draw UI
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# development
|
||||||
|
PRs are always welcome if you feel that something needs improvement/fixing! Make sure to follow [Conventional Commits](https://www.conventionalcommits.org/) and to run tests first, though.
|
||||||
|
|
||||||
|
Run tests using `gyro build test`. Note that tests are run on the native target, not WASM.
|
29
build.zig
Normal file
29
build.zig
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const pkgs = @import("deps.zig").pkgs;
|
||||||
|
|
||||||
|
pub fn build(b: *std.build.Builder) void {
|
||||||
|
const docs = b.option(bool, "docs", "emit docs") orelse false;
|
||||||
|
// Standard release options allow the person running `zig build` to select
|
||||||
|
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
|
||||||
|
const mode = b.standardReleaseOptions();
|
||||||
|
|
||||||
|
const lib = b.addStaticLibrary("zellzig", "src/main.zig");
|
||||||
|
lib.setBuildMode(mode);
|
||||||
|
lib.target.cpu_arch = .wasm32;
|
||||||
|
lib.target.os_tag = .wasi;
|
||||||
|
|
||||||
|
pkgs.addAllTo(lib);
|
||||||
|
|
||||||
|
if (docs) {
|
||||||
|
lib.emit_docs = .{ .emit_to = "zig-out/docs" };
|
||||||
|
}
|
||||||
|
|
||||||
|
lib.install();
|
||||||
|
|
||||||
|
const main_tests = b.addTest("src/main.zig");
|
||||||
|
main_tests.setBuildMode(mode);
|
||||||
|
pkgs.addAllTo(main_tests);
|
||||||
|
|
||||||
|
const test_step = b.step("test", "Run library tests");
|
||||||
|
test_step.dependOn(&main_tests.step);
|
||||||
|
}
|
9
example/.gitignore
vendored
Normal file
9
example/.gitignore
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# Zig
|
||||||
|
zig-*
|
||||||
|
|
||||||
|
# Packages
|
||||||
|
deps.zig
|
||||||
|
|
||||||
|
# Gyro
|
||||||
|
.gyro/
|
||||||
|
gyro.lock
|
12
example/README.md
Normal file
12
example/README.md
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# example
|
||||||
|
This is an example of a simple zellij plugin in zig.
|
||||||
|
It's a super simple status bar.
|
||||||
|
|
||||||
|
If you plan on making your own plugin, be sure to pay special attention to `build.zig` and `gyro.zzz` to configure dependancies and WASM compilation.
|
||||||
|
|
||||||
|
# building & running
|
||||||
|
```bash
|
||||||
|
gyro build -Drelease-fast
|
||||||
|
zellij --layout-path plugin.yaml
|
||||||
|
```
|
||||||
|
|
22
example/build.zig
Normal file
22
example/build.zig
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const deps = @import("deps.zig");
|
||||||
|
|
||||||
|
pub fn build(b: *std.build.Builder) void {
|
||||||
|
// Standard release options allow the person running `zig build` to select
|
||||||
|
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
|
||||||
|
const mode = b.standardReleaseOptions();
|
||||||
|
|
||||||
|
const lib = b.addSharedLibrary("example", "src/main.zig", .{ .unversioned = {} });
|
||||||
|
lib.setBuildMode(mode);
|
||||||
|
lib.target.cpu_arch = .wasm32;
|
||||||
|
lib.target.os_tag = .wasi;
|
||||||
|
deps.pkgs.addAllTo(lib);
|
||||||
|
lib.install();
|
||||||
|
|
||||||
|
const main_tests = b.addTest("src/main.zig");
|
||||||
|
main_tests.setBuildMode(mode);
|
||||||
|
deps.pkgs.addAllTo(main_tests);
|
||||||
|
|
||||||
|
const test_step = b.step("test", "Run library tests");
|
||||||
|
test_step.dependOn(&main_tests.step);
|
||||||
|
}
|
10
example/gyro.zzz
Normal file
10
example/gyro.zzz
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
pkgs:
|
||||||
|
zellzig-example:
|
||||||
|
version: 0.0.0
|
||||||
|
description: A basic zellzig plugin
|
||||||
|
license: GPL-3.0
|
||||||
|
root: src/main.zig
|
||||||
|
deps:
|
||||||
|
zellzig:
|
||||||
|
local: ..
|
||||||
|
root: src/main.zig
|
22
example/plugin.yaml
Normal file
22
example/plugin.yaml
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
template:
|
||||||
|
direction: Horizontal
|
||||||
|
parts:
|
||||||
|
- direction: Vertical
|
||||||
|
borderless: true
|
||||||
|
split_size:
|
||||||
|
Fixed: 1
|
||||||
|
run:
|
||||||
|
plugin:
|
||||||
|
location: "zellij:tab-bar"
|
||||||
|
|
||||||
|
- direction: Vertical
|
||||||
|
plugin: "zig-out/lib/example.wasm"
|
||||||
|
|
||||||
|
- direction: Vertical
|
||||||
|
borderless: true
|
||||||
|
split_size:
|
||||||
|
Fixed: 1
|
||||||
|
run:
|
||||||
|
plugin:
|
||||||
|
location: "file:zig-out/lib/example.wasm"
|
49
example/src/main.zig
Normal file
49
example/src/main.zig
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const zz = @import("zellzig");
|
||||||
|
|
||||||
|
comptime {
|
||||||
|
// This function creates all the exports needed by zellij plugins.
|
||||||
|
// As a paremeter, you should pass the struct that contains the
|
||||||
|
// `init`, `update` and `render` functions. It is recommended
|
||||||
|
// to put these functions in your root file, and pass the root
|
||||||
|
// struct by using `@This()`.
|
||||||
|
zz.createPlugin(@This());
|
||||||
|
}
|
||||||
|
|
||||||
|
// assign out allocator to make sure it doesn't get free'd once init returns
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
var mode: ?zz.types.InputMode = null;
|
||||||
|
|
||||||
|
// called on startup
|
||||||
|
pub fn init() void {
|
||||||
|
// set zellzig's allocator
|
||||||
|
// This is required to receive events.
|
||||||
|
zz.allocator = gpa.allocator();
|
||||||
|
|
||||||
|
// This is required to make zellij close once everything but our plugin is gone.
|
||||||
|
zz.api.setSelectable(false);
|
||||||
|
|
||||||
|
// Make sure we get events.
|
||||||
|
zz.api.subscribe(&[_]zz.types.EventType{.ModeUpdate}) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
// called on every event
|
||||||
|
pub fn update(ev: zz.Event) void {
|
||||||
|
switch (ev) {
|
||||||
|
.ModeUpdate => |mode_info| mode = mode_info.mode,
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// called to draw the UI
|
||||||
|
pub fn render(rows: i32, cols: i32) void {
|
||||||
|
_ = rows;
|
||||||
|
_ = cols;
|
||||||
|
|
||||||
|
if (mode) |m| {
|
||||||
|
var out = std.io.getStdOut();
|
||||||
|
var writer = out.writer();
|
||||||
|
writer.writeAll("Super sophisticated status bar: ") catch {};
|
||||||
|
writer.writeAll(@tagName(m)) catch {};
|
||||||
|
}
|
||||||
|
}
|
18
gyro.zzz
Normal file
18
gyro.zzz
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
pkgs:
|
||||||
|
zellzig:
|
||||||
|
version: 0.0.0
|
||||||
|
description: Build zellij plugins in zig
|
||||||
|
license: GPL-3.0
|
||||||
|
source_url: "https://mzte.de/git/LordMZTE/zellzig"
|
||||||
|
root: src/main.zig
|
||||||
|
deps:
|
||||||
|
getty:
|
||||||
|
git:
|
||||||
|
url: "https://github.com/getty-zig/getty.git"
|
||||||
|
ref: 69b4df59511203f91d38660794d8cb7a073eb815
|
||||||
|
root: src/lib.zig
|
||||||
|
json:
|
||||||
|
git:
|
||||||
|
url: "https://github.com/lordmzte/json.git"
|
||||||
|
ref: fix/math-changes
|
||||||
|
root: src/lib.zig
|
64
src/api.zig
Normal file
64
src/api.zig
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const zz = @import("main.zig");
|
||||||
|
const json = @import("json");
|
||||||
|
const zapi = @import("zellij_api.zig");
|
||||||
|
const types = @import("types.zig");
|
||||||
|
|
||||||
|
pub fn sendObj(data: anytype) !void {
|
||||||
|
var stdout = std.io.getStdOut();
|
||||||
|
try json.toWriter(data, stdout.writer());
|
||||||
|
try stdout.writeAll("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn recvObj(comptime T: type) !types.OwnedDeserData(T) {
|
||||||
|
var buf: [4096]u8 = undefined;
|
||||||
|
var stdin = std.io.getStdIn();
|
||||||
|
const data = (try stdin.reader().readUntilDelimiterOrEof(&buf, '\n')) orelse unreachable;
|
||||||
|
return types.OwnedDeserData(T).deserialize(zz.allocator.?, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscription Handling
|
||||||
|
pub fn subscribe(event_types: []const types.EventType) !void {
|
||||||
|
try sendObj(event_types);
|
||||||
|
zapi.host_subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unsubscribe(event_types: []const types.EventType) !void {
|
||||||
|
try sendObj(event_types);
|
||||||
|
zapi.host_unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plugin Settings
|
||||||
|
pub fn setSelectable(selectable: bool) void {
|
||||||
|
zapi.host_set_selectable(@boolToInt(selectable));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query Functions
|
||||||
|
pub fn getPluginIds() !types.OwnedDeserData(types.PluginIds) {
|
||||||
|
zapi.host_get_plugin_ids();
|
||||||
|
return try recvObj(types.PluginIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getZellijVersion() !types.OwnedDeserData([]const u8) {
|
||||||
|
zapi.host_get_zellij_version();
|
||||||
|
return try recvObj([]const u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn openFile(path: []const u8) !void {
|
||||||
|
try sendObj(path);
|
||||||
|
zapi.host_open_file();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn switchTabTo(tab_idx: u32) void {
|
||||||
|
zapi.host_switch_tab_to(tab_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setTimeout(secs: f64) void {
|
||||||
|
zapi.host_set_timeout(secs);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execCmd(cmd: []const []const u8) !void {
|
||||||
|
try sendObj(cmd);
|
||||||
|
zapi.host_exec_cmd();
|
||||||
|
}
|
||||||
|
|
53
src/deser/char_or_arrow_db.zig
Normal file
53
src/deser/char_or_arrow_db.zig
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const getty = @import("getty");
|
||||||
|
const types = @import("../types.zig");
|
||||||
|
|
||||||
|
const Vis = struct {
|
||||||
|
pub usingnamespace getty.de.Visitor(
|
||||||
|
@This(),
|
||||||
|
types.CharOrArrow,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
visitString,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
pub fn visitString(
|
||||||
|
_: @This(),
|
||||||
|
_: ?std.mem.Allocator,
|
||||||
|
comptime Deserializer: type,
|
||||||
|
input: anytype,
|
||||||
|
) Deserializer.Error!types.CharOrArrow {
|
||||||
|
if (input.len == 1) {
|
||||||
|
return types.CharOrArrow{ .Char = input[0] };
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.CharOrArrow{
|
||||||
|
.Direction = std.meta.stringToEnum(types.Direction, input) orelse
|
||||||
|
return error.UnknownVariant,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn is(comptime T: type) bool {
|
||||||
|
return T == types.CharOrArrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Visitor(comptime _: type) type {
|
||||||
|
return Vis;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize(
|
||||||
|
alloc: ?std.mem.Allocator,
|
||||||
|
comptime _: type,
|
||||||
|
deserializer: anytype,
|
||||||
|
visitor: anytype,
|
||||||
|
) !@TypeOf(visitor).Value {
|
||||||
|
return try deserializer.deserializeString(alloc, visitor);
|
||||||
|
}
|
54
src/deser/line_and_column_db.zig
Normal file
54
src/deser/line_and_column_db.zig
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const getty = @import("getty");
|
||||||
|
const types = @import("../types.zig");
|
||||||
|
|
||||||
|
const Vis = struct {
|
||||||
|
pub usingnamespace getty.de.Visitor(
|
||||||
|
@This(),
|
||||||
|
types.LineAndColumn,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
visitSeq,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
pub fn visitSeq(
|
||||||
|
_: @This(),
|
||||||
|
alloc: ?std.mem.Allocator,
|
||||||
|
comptime Deserializer: type,
|
||||||
|
input: anytype,
|
||||||
|
) Deserializer.Error!types.LineAndColumn {
|
||||||
|
const line = (try input.nextElement(alloc, isize)) orelse
|
||||||
|
return error.InvalidLength;
|
||||||
|
const column = (try input.nextElement(alloc, usize)) orelse
|
||||||
|
return error.InvalidLength;
|
||||||
|
|
||||||
|
return types.LineAndColumn{
|
||||||
|
.line = line,
|
||||||
|
.column = column,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn is(comptime T: type) bool {
|
||||||
|
return T == types.LineAndColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Visitor(comptime _: type) type {
|
||||||
|
return Vis;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize(
|
||||||
|
alloc: ?std.mem.Allocator,
|
||||||
|
comptime _: type,
|
||||||
|
deserializer: anytype,
|
||||||
|
visitor: anytype,
|
||||||
|
) !@TypeOf(visitor).Value {
|
||||||
|
return try deserializer.deserializeSeq(alloc, visitor);
|
||||||
|
}
|
150
src/deser/union_db.zig
Normal file
150
src/deser/union_db.zig
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const getty = @import("getty");
|
||||||
|
const types = @import("../types.zig");
|
||||||
|
|
||||||
|
pub fn is(comptime T: type) bool {
|
||||||
|
return switch (@typeInfo(T)) {
|
||||||
|
.Union => true,
|
||||||
|
else => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Visitor(comptime T: type) type {
|
||||||
|
return struct {
|
||||||
|
pub usingnamespace getty.de.Visitor(
|
||||||
|
@This(),
|
||||||
|
T,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
visitMap,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
visitString,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
pub fn visitMap(
|
||||||
|
_: @This(),
|
||||||
|
alloc: ?std.mem.Allocator,
|
||||||
|
comptime Deserializer: type,
|
||||||
|
input: anytype,
|
||||||
|
) Deserializer.Error!T {
|
||||||
|
const tag = (try input.nextKey(alloc, []const u8)) orelse
|
||||||
|
return error.InvalidLength;
|
||||||
|
|
||||||
|
var val: ?T = null;
|
||||||
|
inline for (@typeInfo(T).Union.fields) |field| {
|
||||||
|
if (val == null and std.mem.eql(u8, field.name, tag)) {
|
||||||
|
if (field.field_type == void) {
|
||||||
|
return error.InvalidType;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = try input.nextValue(alloc, field.field_type);
|
||||||
|
val = @unionInit(T, field.name, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val) |v| {
|
||||||
|
while (try input.nextKey(alloc, []const u8)) |_| {}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
return error.UnknownVariant;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn visitString(
|
||||||
|
_: @This(),
|
||||||
|
_: ?std.mem.Allocator,
|
||||||
|
comptime Deserializer: type,
|
||||||
|
input: anytype,
|
||||||
|
) Deserializer.Error!T {
|
||||||
|
inline for (@typeInfo(T).Union.fields) |field| {
|
||||||
|
if (std.mem.eql(u8, field.name, input))
|
||||||
|
if (field.field_type == void)
|
||||||
|
return @unionInit(T, field.name, {})
|
||||||
|
else
|
||||||
|
return error.InvalidType;
|
||||||
|
}
|
||||||
|
|
||||||
|
return error.UnknownVariant;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize(
|
||||||
|
alloc: ?std.mem.Allocator,
|
||||||
|
comptime _: type,
|
||||||
|
deserializer: anytype,
|
||||||
|
visitor: anytype,
|
||||||
|
) !@TypeOf(visitor).Value {
|
||||||
|
// This horrifying hack is needed since just trying deserializeString
|
||||||
|
// would consume tokens needed by deserializeMap
|
||||||
|
var tokens_copy = deserializer.context.tokens;
|
||||||
|
const token = (try tokens_copy.next()) orelse
|
||||||
|
return error.UnexpectedEndOfJson;
|
||||||
|
|
||||||
|
return switch (token) {
|
||||||
|
.String => try deserializer.deserializeString(alloc, visitor),
|
||||||
|
.ObjectBegin => try deserializer.deserializeMap(alloc, visitor),
|
||||||
|
else => error.InvalidType,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "union_db deserialize" {
|
||||||
|
const TestUnion = union(enum) {
|
||||||
|
Foo: u8,
|
||||||
|
Bar: struct { a: u8, b: u8 },
|
||||||
|
Baz: void,
|
||||||
|
};
|
||||||
|
|
||||||
|
const json_src_a =
|
||||||
|
\\ {
|
||||||
|
\\ "Foo": 42
|
||||||
|
\\ }
|
||||||
|
;
|
||||||
|
|
||||||
|
const json_src_b =
|
||||||
|
\\ {
|
||||||
|
\\ "Bar": {
|
||||||
|
\\ "a": 42,
|
||||||
|
\\ "b": 69
|
||||||
|
\\ }
|
||||||
|
\\ }
|
||||||
|
;
|
||||||
|
|
||||||
|
const json_src_c =
|
||||||
|
\\"Baz"
|
||||||
|
;
|
||||||
|
|
||||||
|
var deser_a = try types.OwnedDeserData(TestUnion).deserialize(
|
||||||
|
std.testing.allocator,
|
||||||
|
json_src_a,
|
||||||
|
);
|
||||||
|
defer deser_a.deinit();
|
||||||
|
|
||||||
|
var deser_b = try types.OwnedDeserData(TestUnion).deserialize(
|
||||||
|
std.testing.allocator,
|
||||||
|
json_src_b,
|
||||||
|
);
|
||||||
|
defer deser_b.deinit();
|
||||||
|
|
||||||
|
var deser_c = try types.OwnedDeserData(TestUnion).deserialize(
|
||||||
|
std.testing.allocator,
|
||||||
|
json_src_c,
|
||||||
|
);
|
||||||
|
defer deser_c.deinit();
|
||||||
|
|
||||||
|
try std.testing.expectEqual(TestUnion{ .Foo = 42 }, deser_a.data);
|
||||||
|
|
||||||
|
try std.testing.expectEqual(TestUnion{
|
||||||
|
.Bar = .{
|
||||||
|
.a = 42,
|
||||||
|
.b = 69,
|
||||||
|
},
|
||||||
|
}, deser_b.data);
|
||||||
|
|
||||||
|
try std.testing.expectEqual(TestUnion{ .Baz = {} }, deser_c.data);
|
||||||
|
}
|
65
src/main.zig
Normal file
65
src/main.zig
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
const std = @import("std");
|
||||||
|
pub const types = @import("types.zig");
|
||||||
|
pub const Event = types.Event;
|
||||||
|
pub const api = @import("api.zig");
|
||||||
|
|
||||||
|
// This is the allocator that will be used by zellzig for communication.
|
||||||
|
// This must be set before events can be received.
|
||||||
|
pub var allocator: ?std.mem.Allocator = null;
|
||||||
|
|
||||||
|
comptime {
|
||||||
|
if (@import("builtin").is_test)
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a plugin that can be called into by zellij. This function should take a
|
||||||
|
/// struct type with the functions of your plugin. Usage of this function should
|
||||||
|
/// look like this:
|
||||||
|
/// ```zig
|
||||||
|
/// // main.zig
|
||||||
|
/// const zz = @import("zellzig");
|
||||||
|
/// comptime {
|
||||||
|
/// zz.createPlugin(@This());
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// pub fn init() void {
|
||||||
|
/// const alloc = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
/// zz.allocator = alloc.allocator();
|
||||||
|
/// }
|
||||||
|
/// pub fn update() void {}
|
||||||
|
/// pub fn render(rows: i32, cols: i32) void {}
|
||||||
|
/// ```
|
||||||
|
pub fn createPlugin(comptime Plugin: type) void {
|
||||||
|
if (@TypeOf(Plugin.init) != fn () void)
|
||||||
|
@compileError("Function 'init' has invalid signature!");
|
||||||
|
|
||||||
|
if (@TypeOf(Plugin.update) != fn (Event) void)
|
||||||
|
@compileError("Function 'update' has invalid signature!");
|
||||||
|
|
||||||
|
if (@TypeOf(Plugin.render) != fn (i32, i32) void)
|
||||||
|
@compileError("Function 'render' has invalid signature!");
|
||||||
|
|
||||||
|
_ = struct {
|
||||||
|
export fn _start() void {
|
||||||
|
Plugin.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
export fn render(rows: i32, cols: i32) void {
|
||||||
|
Plugin.render(rows, cols);
|
||||||
|
}
|
||||||
|
|
||||||
|
export fn update() void {
|
||||||
|
if (allocator == null) {
|
||||||
|
@panic("Got event while allocator is null! Did you forget to set it?");
|
||||||
|
}
|
||||||
|
|
||||||
|
var ev = api.recvObj(types.Event) catch |err| {
|
||||||
|
std.log.err("Deserialize error: {}", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
defer ev.deinit();
|
||||||
|
|
||||||
|
Plugin.update(ev.data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
379
src/types.zig
Normal file
379
src/types.zig
Normal file
|
@ -0,0 +1,379 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const getty = @import("getty");
|
||||||
|
const json = @import("json");
|
||||||
|
|
||||||
|
/// getty deserialization blocks required to properly deserialize messages
|
||||||
|
pub const dbs = .{
|
||||||
|
@import("deser/char_or_arrow_db.zig"),
|
||||||
|
@import("deser/line_and_column_db.zig"),
|
||||||
|
@import("deser/union_db.zig"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// required because the only way to properly free deserilized
|
||||||
|
// json is using an arena alloc.
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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": "tasteful-root"
|
||||||
|
\\ }
|
||||||
|
\\}
|
||||||
|
;
|
||||||
|
|
||||||
|
var deser = try OwnedDeserData(Event).deserialize(std.testing.allocator, json_src);
|
||||||
|
defer deser.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const ModeInfo = struct {
|
||||||
|
mode: InputMode,
|
||||||
|
keybinds: [][2][]const u8,
|
||||||
|
style: Style,
|
||||||
|
capabilities: PluginCapabilities,
|
||||||
|
session_name: ?[]const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const InputMode = enum {
|
||||||
|
Normal,
|
||||||
|
Locked,
|
||||||
|
Resize,
|
||||||
|
Pane,
|
||||||
|
Tab,
|
||||||
|
Scroll,
|
||||||
|
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) {
|
||||||
|
Backspace,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
Home,
|
||||||
|
End,
|
||||||
|
PageUp,
|
||||||
|
PageDown,
|
||||||
|
BackTab,
|
||||||
|
Delete,
|
||||||
|
Insert,
|
||||||
|
F: u8,
|
||||||
|
Char: []u8,
|
||||||
|
Alt: CharOrArrow,
|
||||||
|
Ctrl: []u8,
|
||||||
|
Null,
|
||||||
|
Esc,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const CharOrArrow = union(enum) {
|
||||||
|
Char: u8,
|
||||||
|
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 = '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,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: implement deserialization block
|
||||||
|
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,
|
||||||
|
};
|
10
src/zellij_api.zig
Normal file
10
src/zellij_api.zig
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
pub extern "zellij" fn host_subscribe() void;
|
||||||
|
pub extern "zellij" fn host_unsubscribe() void;
|
||||||
|
pub extern "zellij" fn host_set_selectable(selectable: i32) void;
|
||||||
|
pub extern "zellij" fn host_get_plugin_ids() void;
|
||||||
|
pub extern "zellij" fn host_get_zellij_version() void;
|
||||||
|
pub extern "zellij" fn host_open_file() void;
|
||||||
|
pub extern "zellij" fn host_switch_tab_to(tab_idx: u32) void;
|
||||||
|
pub extern "zellij" fn host_set_timeout(secs: f64) void;
|
||||||
|
pub extern "zellij" fn host_exec_cmd() void;
|
||||||
|
|
Loading…
Reference in a new issue