Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/PLUGINS.md
Original file line number Diff line number Diff line change
Expand Up @@ -1720,6 +1720,11 @@ drop straight into the plugins directory, exactly like §2.6.
| `fizzy_plugin_set_dvui_context` | Host injects live dvui window/io before draw |
| `fizzy_plugin_set_render_bridge` | Host injects the dvui proxy render bridge |

The generated root also declares the dylib's `std_options` (its logging reaches fizzy's Output
panel, `sdk.dylib.stdOptions`) and its `panic` (`sdk.dylib.panic`): a panic in plugin code goes
to fizzy's own handler through `EditorAPI.panic`, so fizzy's crash report names the plugin and
carries the message.

### Plugin dylib layout

```
Expand Down
1 change: 1 addition & 0 deletions plugins/shared/build/helpers.zig
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ fn generatedDylibRoot(
\\const plugin = @import("plugin_impl");
\\const opts = plugin.plugin_options;
\\pub const std_options: std.Options = sdk.dylib.stdOptions(opts.id);
\\pub const panic = sdk.dylib.panic;
\\comptime {
\\ sdk.dylib.exportEntry(plugin, .{
\\ .id = opts.id,
Expand Down
1 change: 1 addition & 0 deletions sdk/plugin_sdk.zig
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,7 @@ fn generatedDylibRoot(
\\const plugin = @import("plugin_impl");
\\const opts = plugin.plugin_options;
\\pub const std_options: std.Options = sdk.dylib.stdOptions(opts.id);
\\pub const panic = sdk.dylib.panic;
\\comptime {
\\ sdk.dylib.exportEntry(plugin, .{
\\ .id = opts.id,
Expand Down
10 changes: 10 additions & 0 deletions sdk/src/EditorAPI.zig
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,12 @@ pub const VTable = struct {
/// process's stderr).
logLine: *const fn (ctx: *anyopaque, level: std.log.Level, scope: []const u8, message: []const u8) void,

/// A plugin dylib's panic, handed to the app's own panic handler (`sdk.dylib.panic`). A
/// dylib compiles its own std, whose handler would print the message to a stderr nobody reads
/// and abort; through here the message reaches the app's crash report, and on the web the
/// console. `first_trace_addr` is where the stack trace starts, in the plugin's frames.
panic: *const fn (ctx: *anyopaque, msg: []const u8, first_trace_addr: ?usize) noreturn,

/// Draws a standard menu-item row (separator above, label + keybind hint, click-detect)
/// inside whatever menu is currently open, and returns whether it was clicked this frame.
/// `command_id` is the registered `Command` this row runs (e.g. `"text.format"`), or null
Expand Down Expand Up @@ -754,6 +760,10 @@ pub fn logLine(self: EditorAPI, level: std.log.Level, scope: []const u8, message
self.vtable.logLine(self.ctx, level, scope, message);
}

pub fn panic(self: EditorAPI, msg: []const u8, first_trace_addr: ?usize) noreturn {
self.vtable.panic(self.ctx, msg, first_trace_addr);
}

pub fn drawMenuItem(self: EditorAPI, title: []const u8, command_id: ?[]const u8) bool {
return self.vtable.drawMenuItem(self.ctx, title, command_id);
}
Expand Down
12 changes: 12 additions & 0 deletions sdk/src/dylib.zig
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,18 @@ pub fn stdOptions(comptime id: []const u8) std.Options {
return .{ .logFn = Impl.logFn };
}

/// The dylib's panic handler, which the generated root declares (`pub const panic =
/// sdk.dylib.panic`): the panic goes to the app's (`EditorAPI.panic`), so a crash report carries
/// the message and the plugin's frames. Before the host is installed (nothing of the plugin's has
/// run yet), std's own.
pub const panic = std.debug.FullPanic(forwardPanic);

fn forwardPanic(msg: []const u8, first_trace_addr: ?usize) noreturn {
@branchHint(.cold);
if (runtime.installedHost()) |h| if (h.fizzy_api) |api| api.panic(msg, first_trace_addr orelse @returnAddress());
std.debug.defaultPanic(msg, first_trace_addr);
}

/// Identity passed to `exportEntry` — built-injected from `plugin.zig.zon` (see `readManifest`
/// in `plugin_sdk.zig` / `helpers.zig`), never author-supplied `Zig` source. `version`/
/// `min_sdk_version` are typed `SemanticVersion` (not strings) because the generated dylib root
Expand Down
10 changes: 8 additions & 2 deletions sdk/src/runtime.zig
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const dvui = @import("dvui");
const Host = @import("Host.zig");

var gpa: std.mem.Allocator = undefined;
var host_ptr: *Host = undefined;
var host_ptr: ?*Host = null;
/// Fizzy-owned plugin state injected before `register` (built-in static/dylib path).
var injected_state: ?*anyopaque = null;

Expand All @@ -24,6 +24,12 @@ pub fn allocator() std.mem.Allocator {

/// Fizzy `*Host` — registries, services, and the `EditorAPI` read surface.
pub fn host() *Host {
return host_ptr.?;
}

/// The `*Host`, or null before fizzy has installed it: for code that may run before
/// `register`, such as the panic handler.
pub fn installedHost() ?*Host {
return host_ptr;
}

Expand Down Expand Up @@ -52,7 +58,7 @@ pub fn installRuntime(
}

fn hostFrostPane(id: dvui.Id, rect: dvui.Rect.Physical, corners: dvui.CornerRect, scale: f32) bool {
const api = host_ptr.fizzy_api orelse return false;
const api = host_ptr.?.fizzy_api orelse return false;
return api.frostPane(id, rect, corners, scale);
}

Expand Down
2 changes: 1 addition & 1 deletion sdk/src/version.zig
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ pub const sdk_version = @import("sdk_version").sdk_version;
/// why it is a single target/mode-invariant literal rather than a per-target table. Update this
/// value from the `@compileError` it triggers whenever it changes; leave `sdk_version` to the
/// next SDK release.
pub const recorded_sdk_shape_fingerprint: u64 = 0x1bf417f650c67637;
pub const recorded_sdk_shape_fingerprint: u64 = 0x65096e9408ddb32f;

comptime {
if (dylib.sdk_shape_fingerprint != recorded_sdk_shape_fingerprint) {
Expand Down
7 changes: 7 additions & 0 deletions src/editor/Editor.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2312,6 +2312,7 @@ const fizzy_api_vtable: sdk.EditorAPI.VTable = .{
.resumeSaveAllQuit = fizzyResumeSaveAllQuit,
.abortSaveAllQuit = fizzyAbortSaveAllQuit,
.logLine = fizzyLogLine,
.panic = fizzyPanic,
.drawMenuItem = fizzyDrawMenuItem,
.askCommandArguments = fizzyAskCommandArguments,
.loadPluginSettingsFile = fizzyLoadPluginSettingsFile,
Expand All @@ -2329,6 +2330,12 @@ fn fizzyLogLine(ctx: *anyopaque, level: std.log.Level, scope: []const u8, messag
fizzy.OutputLog.appendLine(level, scope, message);
}

/// A plugin's panic, raised again as the app's own: the root's handler writes the crash report
/// (`app.crash`), or on the web logs it to the console.
fn fizzyPanic(_: *anyopaque, msg: []const u8, first_trace_addr: ?usize) noreturn {
std.debug.panicExtra(first_trace_addr, "{s}", .{msg});
}

/// See `EditorAPI.VTable.drawMenuItem`'s doc comment for why this widget construction has to
/// happen here (in fizzy) rather than in the calling plugin.
///
Expand Down
Loading