mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-19 10:16:12 +03:00
185 lines
5.8 KiB
Zig
185 lines
5.8 KiB
Zig
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
const adw = @import("adw");
|
|
const gobject = @import("gobject");
|
|
const gtk = @import("gtk");
|
|
|
|
const configpkg = @import("../../../config.zig");
|
|
const CoreConfig = configpkg.Config;
|
|
|
|
const log = std.log.scoped(.gtk_ghostty_config);
|
|
|
|
/// Wraps a `Ghostty.Config` object in a GObject so it can be reference
|
|
/// counted. When this object is freed, the underlying config is also freed.
|
|
///
|
|
/// It is highly recommended to NOT take a reference to this object,
|
|
/// since configuration takes up a lot of memory (relatively). Instead,
|
|
/// receivers of this should usually create a `DerivedConfig` struct from
|
|
/// this, copy any memory they require, and own that structure instead.
|
|
///
|
|
/// This can also expose helpers to access configuration in ways that
|
|
/// may be more egonomic to GTK primitives.
|
|
pub const Config = extern struct {
|
|
const Self = @This();
|
|
parent_instance: Parent,
|
|
pub const Parent = gobject.Object;
|
|
pub const getGObjectType = gobject.ext.defineClass(Self, .{
|
|
.name = "GhosttyConfig",
|
|
.classInit = &Class.init,
|
|
.parent_class = &Class.parent,
|
|
.private = .{ .Type = Private, .offset = &Private.offset },
|
|
});
|
|
|
|
pub const properties = struct {
|
|
pub const @"diagnostics-buffer" = gobject.ext.defineProperty(
|
|
"diagnostics-buffer",
|
|
Self,
|
|
?*gtk.TextBuffer,
|
|
.{
|
|
.nick = "Dignostics Buffer",
|
|
.blurb = "A TextBuffer that contains the diagnostics.",
|
|
.default = null,
|
|
.accessor = .{
|
|
.getter = Self.diagnosticsBuffer,
|
|
},
|
|
},
|
|
);
|
|
|
|
pub const @"has-diagnostics" = gobject.ext.defineProperty(
|
|
"has-diagnostics",
|
|
Self,
|
|
bool,
|
|
.{
|
|
.nick = "has-diagnostics",
|
|
.blurb = "Whether the configuration has diagnostics.",
|
|
.default = false,
|
|
.accessor = .{
|
|
.getter = Self.hasDiagnostics,
|
|
},
|
|
},
|
|
);
|
|
};
|
|
|
|
const Private = struct {
|
|
config: CoreConfig,
|
|
|
|
var offset: c_int = 0;
|
|
};
|
|
|
|
/// Create a new GhosttyConfig from a loaded configuration.
|
|
///
|
|
/// This clones the given configuration, so it is safe for the
|
|
/// caller to free the original configuration after this call.
|
|
pub fn new(alloc: Allocator, config: *const CoreConfig) Allocator.Error!*Self {
|
|
const self = gobject.ext.newInstance(Self, .{});
|
|
errdefer self.unref();
|
|
|
|
const priv = self.private();
|
|
priv.config = try config.clone(alloc);
|
|
|
|
return self;
|
|
}
|
|
|
|
/// Get the wrapped configuration. It's unsafe to store this or access
|
|
/// it in any way that may live beyond the lifetime of this object.
|
|
pub fn get(self: *Self) *const CoreConfig {
|
|
return &self.private().config;
|
|
}
|
|
|
|
/// Get the mutable configuration. This is usually NOT recommended
|
|
/// because any changes to the config won't be propagated to anyone
|
|
/// with a reference to this object. If you know what you're doing, then
|
|
/// you can use this.
|
|
pub fn getMut(self: *Self) *CoreConfig {
|
|
return &self.private().config;
|
|
}
|
|
|
|
/// Returns whether this configuration has any diagnostics.
|
|
pub fn hasDiagnostics(self: *Self) bool {
|
|
const config = self.get();
|
|
return !config._diagnostics.empty();
|
|
}
|
|
|
|
/// Reads the diagnostics of this configuration as a TextBuffer,
|
|
/// or returns null if there are no diagnostics.
|
|
pub fn diagnosticsBuffer(self: *Self) ?*gtk.TextBuffer {
|
|
const config = self.get();
|
|
if (config._diagnostics.empty()) return null;
|
|
|
|
const text_buf: *gtk.TextBuffer = .new(null);
|
|
errdefer text_buf.unref();
|
|
|
|
var buf: [4095:0]u8 = undefined;
|
|
var fbs = std.io.fixedBufferStream(&buf);
|
|
for (config._diagnostics.items()) |diag| {
|
|
fbs.reset();
|
|
diag.write(fbs.writer()) catch |err| {
|
|
log.warn(
|
|
"error writing diagnostic to buffer err={}",
|
|
.{err},
|
|
);
|
|
continue;
|
|
};
|
|
|
|
text_buf.insertAtCursor(&buf, @intCast(fbs.pos));
|
|
text_buf.insertAtCursor("\n", 1);
|
|
}
|
|
|
|
return text_buf;
|
|
}
|
|
|
|
fn finalize(self: *Self) callconv(.C) void {
|
|
self.private().config.deinit();
|
|
|
|
gobject.Object.virtual_methods.finalize.call(
|
|
Class.parent,
|
|
self.as(Parent),
|
|
);
|
|
}
|
|
|
|
pub fn as(self: *Self, comptime T: type) *T {
|
|
return gobject.ext.as(T, self);
|
|
}
|
|
|
|
pub fn ref(self: *Self) *Self {
|
|
return @ptrCast(@alignCast(gobject.Object.ref(self.as(gobject.Object))));
|
|
}
|
|
|
|
pub fn unref(self: *Self) void {
|
|
gobject.Object.unref(self.as(gobject.Object));
|
|
}
|
|
|
|
fn private(self: *Self) *Private {
|
|
return gobject.ext.impl_helpers.getPrivate(
|
|
self,
|
|
Private,
|
|
Private.offset,
|
|
);
|
|
}
|
|
|
|
pub const Class = extern struct {
|
|
parent_class: Parent.Class,
|
|
var parent: *Parent.Class = undefined;
|
|
pub const Instance = Self;
|
|
|
|
fn init(class: *Class) callconv(.C) void {
|
|
gobject.Object.virtual_methods.finalize.implement(class, &finalize);
|
|
gobject.ext.registerProperties(class, &.{
|
|
properties.@"diagnostics-buffer",
|
|
properties.@"has-diagnostics",
|
|
});
|
|
}
|
|
};
|
|
};
|
|
|
|
// This test verifies our memory management works as expected. Since
|
|
// we use the testing allocator any leaks are detected.
|
|
test "GhosttyConfig" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
var config: CoreConfig = try .default(alloc);
|
|
defer config.deinit();
|
|
const obj: *Config = try .new(alloc, &config);
|
|
obj.unref();
|
|
}
|