Merge branch 'master' into helix-mode-selections

This commit is contained in:
Meredith Oleander 2025-01-22 13:47:37 +11:00 committed by GitHub
commit 80c8795c3b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 429 additions and 134 deletions

View file

@ -1,6 +1,8 @@
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin"); const builtin = @import("builtin");
const optimize_deps = .ReleaseFast;
pub fn build(b: *std.Build) void { pub fn build(b: *std.Build) void {
const release = b.option(bool, "package_release", "Build all release targets") orelse false; const release = b.option(bool, "package_release", "Build all release targets") orelse false;
const tracy_enabled = b.option(bool, "enable_tracy", "Enable tracy client library (default: no)") orelse false; const tracy_enabled = b.option(bool, "enable_tracy", "Enable tracy client library (default: no)") orelse false;
@ -227,14 +229,14 @@ pub fn build_exe(
const syntax_dep = b.dependency("syntax", .{ const syntax_dep = b.dependency("syntax", .{
.target = target, .target = target,
.optimize = optimize, .optimize = optimize_deps,
.use_tree_sitter = use_tree_sitter, .use_tree_sitter = use_tree_sitter,
}); });
const syntax_mod = syntax_dep.module("syntax"); const syntax_mod = syntax_dep.module("syntax");
const thespian_dep = b.dependency("thespian", .{ const thespian_dep = b.dependency("thespian", .{
.target = target, .target = target,
.optimize = optimize, .optimize = optimize_deps,
.enable_tracy = tracy_enabled, .enable_tracy = tracy_enabled,
}); });
@ -316,16 +318,13 @@ pub fn build_exe(
const renderer_mod = blk: { const renderer_mod = blk: {
if (gui) switch (target.result.os.tag) { if (gui) switch (target.result.os.tag) {
.windows => { .windows => {
const direct2d_dep = b.lazyDependency("direct2d", .{}) orelse break :blk tui_renderer_mod; const win32_dep = b.lazyDependency("win32", .{}) orelse break :blk tui_renderer_mod;
const win32_mod = win32_dep.module("win32");
const win32_dep = direct2d_dep.builder.dependency("win32", .{});
const win32_mod = win32_dep.module("zigwin32");
const gui_mod = b.createModule(.{ const gui_mod = b.createModule(.{
.root_source_file = b.path("src/win32/gui.zig"), .root_source_file = b.path("src/win32/gui.zig"),
.imports = &.{ .imports = &.{
.{ .name = "build_options", .module = options_mod }, .{ .name = "build_options", .module = options_mod },
.{ .name = "win32", .module = win32_mod }, .{ .name = "win32", .module = win32_mod },
.{ .name = "ddui", .module = direct2d_dep.module("ddui") },
.{ .name = "cbor", .module = cbor_mod }, .{ .name = "cbor", .module = cbor_mod },
.{ .name = "thespian", .module = thespian_mod }, .{ .name = "thespian", .module = thespian_mod },
.{ .name = "input", .module = input_mod }, .{ .name = "input", .module = input_mod },
@ -333,6 +332,7 @@ pub fn build_exe(
.{ .name = "vaxis", .module = vaxis_mod }, .{ .name = "vaxis", .module = vaxis_mod },
.{ .name = "color", .module = color_mod }, .{ .name = "color", .module = color_mod },
.{ .name = "gui_config", .module = gui_config_mod }, .{ .name = "gui_config", .module = gui_config_mod },
.{ .name = "tracy", .module = tracy_mod },
}, },
}); });
gui_mod.addIncludePath(b.path("src/win32")); gui_mod.addIncludePath(b.path("src/win32"));

View file

@ -10,16 +10,16 @@
.hash = "1220930a42f8da3fb7f723e3ad3f6dcc6db76327dd8d26274566423192d53e91b2bb", .hash = "1220930a42f8da3fb7f723e3ad3f6dcc6db76327dd8d26274566423192d53e91b2bb",
}, },
.tracy = .{ .tracy = .{
.url = "https://github.com/neurocyte/zig-tracy/archive/58999b786089e5319dd0707f6afbfca04c6340e7.tar.gz", .url = "https://github.com/neurocyte/zig-tracy/archive/e04e31c64498149a324491b8534758e6af43a5c2.tar.gz",
.hash = "1220a2c8f8db1b5265458ac967ea1f7cc0a8ddcd1d774df3b73d86c4f529aadbfb94", .hash = "1220d0fb2bff7b453dbb39d1db3eb472b6680e2564f2b23b0e947671be47bbdd188f",
}, },
.dizzy = .{ .dizzy = .{
.url = "https://github.com/neurocyte/dizzy/archive/455d18369cbb2a0458ba70be919cd378338d695e.tar.gz", .url = "https://github.com/neurocyte/dizzy/archive/455d18369cbb2a0458ba70be919cd378338d695e.tar.gz",
.hash = "1220220dbc7fe91c1c54438193ca765cebbcb7d58f35cdcaee404a9d2245a42a4362", .hash = "1220220dbc7fe91c1c54438193ca765cebbcb7d58f35cdcaee404a9d2245a42a4362",
}, },
.thespian = .{ .thespian = .{
.url = "https://github.com/neurocyte/thespian/archive/b7ace533875217f2b002f0ac8dfe32169cab7138.tar.gz", .url = "https://github.com/neurocyte/thespian/archive/d2b65df7c59b58f5e86b1ce3403cd3589d06a367.tar.gz",
.hash = "1220d25045c26cc8d2d46995d670ce10b64d0e3a3fa23cfded9a35d353b4d08cff08", .hash = "122040a2dcb9569ee26ae9c205f944db4ee9e3eb7c9d5ec610c7d84cf82ab6909fa9",
}, },
.themes = .{ .themes = .{
.url = "https://github.com/neurocyte/flow-themes/releases/download/master-770924d1aba9cc33440b66e865bd4f7dca871074/flow-themes.tar.gz", .url = "https://github.com/neurocyte/flow-themes/releases/download/master-770924d1aba9cc33440b66e865bd4f7dca871074/flow-themes.tar.gz",
@ -37,9 +37,9 @@
.url = "https://github.com/rockorager/zeit/archive/9cca8ec620a54c3b07cd249f25e5bcb3153d03d7.tar.gz", .url = "https://github.com/rockorager/zeit/archive/9cca8ec620a54c3b07cd249f25e5bcb3153d03d7.tar.gz",
.hash = "1220755ea2a5aa6bb3713437aaafefd44812169fe43f1da755c3ee6101b85940f441", .hash = "1220755ea2a5aa6bb3713437aaafefd44812169fe43f1da755c3ee6101b85940f441",
}, },
.direct2d = .{ .win32 = .{
.url = "https://github.com/marler8997/direct2d-zig/archive/0d031389a26653bb71f81c2340d1b8ba6bd339c3.tar.gz", .url = "https://github.com/marlersoft/zigwin32/archive/259b6f353a48968d7e3171573db4fd898b046188.tar.gz",
.hash = "122069b40656962c6ba9b9b3f9f882ba2e9cf4c5e1afebac7b7501404129e6bb4705", .hash = "1220925614447b54ccc9894bbba8b202c6a8b750267890edab7732064867e46f3217",
.lazy = true, .lazy = true,
}, },
}, },

View file

@ -1,12 +1,13 @@
#!/bin/bash #!/bin/bash
set -e set -e
DESTDIR="$(pwd)/release"
BASEDIR="$(cd "$(dirname "$0")/.." && pwd)" BASEDIR="$(cd "$(dirname "$0")/.." && pwd)"
APPNAME="$(basename "$BASEDIR")" APPNAME="$(basename "$BASEDIR")"
cd "$BASEDIR" cd "$BASEDIR"
if [ -e "release" ]; then if [ -e "$DESTDIR" ]; then
echo directory \"release\" already exists echo directory \"release\" already exists
exit 1 exit 1
fi fi
@ -17,9 +18,9 @@ echo running tests...
echo building... echo building...
./zig build -Dpackage_release --prefix release/build ./zig build -Dpackage_release --prefix "$DESTDIR/build"
cd release/build cd "$DESTDIR/build"
VERSION=$(/bin/cat version) VERSION=$(/bin/cat version)
TARGETS=$(/bin/ls) TARGETS=$(/bin/ls)
@ -44,7 +45,7 @@ for tarfile in $TARFILES; do
sha256sum -b "$tarfile" > "${tarfile}.sha256" sha256sum -b "$tarfile" > "${tarfile}.sha256"
done done
echo "done making release $VERSION" echo "done making release $VERSION @ $DESTDIR"
echo echo
/bin/ls -lah /bin/ls -lah

View file

@ -10,6 +10,7 @@ const max_imbalance = 7;
pub const Root = *const Node; pub const Root = *const Node;
pub const unicode = @import("unicode.zig"); pub const unicode = @import("unicode.zig");
pub const Manager = @import("Manager.zig");
pub const Cursor = @import("Cursor.zig"); pub const Cursor = @import("Cursor.zig");
pub const View = @import("View.zig"); pub const View = @import("View.zig");
pub const Selection = @import("Selection.zig"); pub const Selection = @import("Selection.zig");
@ -43,6 +44,9 @@ undo_history: ?*UndoNode = null,
redo_history: ?*UndoNode = null, redo_history: ?*UndoNode = null,
curr_history: ?*UndoNode = null, curr_history: ?*UndoNode = null,
mtime: i64,
utime: i64,
pub const EolMode = enum { lf, crlf }; pub const EolMode = enum { lf, crlf };
pub const EolModeTag = @typeInfo(EolMode).Enum.tag_type; pub const EolModeTag = @typeInfo(EolMode).Enum.tag_type;
@ -143,7 +147,7 @@ pub const Leaf = struct {
bol: bool = true, bol: bool = true,
eol: bool = true, eol: bool = true,
fn new(allocator: Allocator, piece: []const u8, bol: bool, eol: bool) !*const Node { fn new(allocator: Allocator, piece: []const u8, bol: bool, eol: bool) error{OutOfMemory}!*const Node {
if (piece.len == 0) if (piece.len == 0)
return if (!bol and !eol) &empty_leaf else if (bol and !eol) &empty_bol_leaf else if (!bol and eol) &empty_eol_leaf else &empty_line_leaf; return if (!bol and !eol) &empty_leaf else if (bol and !eol) &empty_bol_leaf else if (!bol and eol) &empty_eol_leaf else &empty_line_leaf;
const node = try allocator.create(Node); const node = try allocator.create(Node);
@ -1043,7 +1047,7 @@ const Node = union(enum) {
} }
}; };
pub fn create(allocator: Allocator) !*Self { pub fn create(allocator: Allocator) error{OutOfMemory}!*Self {
const self = try allocator.create(Self); const self = try allocator.create(Self);
const arena_a = if (builtin.is_test) allocator else std.heap.page_allocator; const arena_a = if (builtin.is_test) allocator else std.heap.page_allocator;
self.* = .{ self.* = .{
@ -1051,6 +1055,8 @@ pub fn create(allocator: Allocator) !*Self {
.allocator = self.arena.allocator(), .allocator = self.arena.allocator(),
.external_allocator = allocator, .external_allocator = allocator,
.root = try Node.new(self.allocator, &empty_leaf, &empty_leaf), .root = try Node.new(self.allocator, &empty_leaf, &empty_leaf),
.mtime = std.time.milliTimestamp(),
.utime = std.time.milliTimestamp(),
}; };
return self; return self;
} }
@ -1062,12 +1068,27 @@ pub fn deinit(self: *Self) void {
self.external_allocator.destroy(self); self.external_allocator.destroy(self);
} }
fn new_file(self: *const Self, file_exists: *bool) !Root { pub fn update_last_used_time(self: *Self) void {
self.utime = std.time.milliTimestamp();
}
fn new_file(self: *const Self, file_exists: *bool) error{OutOfMemory}!Root {
file_exists.* = false; file_exists.* = false;
return Leaf.new(self.allocator, "", true, false); return Leaf.new(self.allocator, "", true, false);
} }
pub fn load(self: *const Self, reader: anytype, size: usize, eol_mode: *EolMode, utf8_sanitized: *bool) !Root { pub fn LoadError(comptime reader_error: anytype) type {
return error{
OutOfMemory,
BufferUnderrun,
DanglingSurrogateHalf,
ExpectedSecondSurrogateHalf,
UnexpectedSecondSurrogateHalf,
Unexpected,
} || reader_error;
}
pub fn load(self: *const Self, reader: anytype, size: usize, eol_mode: *EolMode, utf8_sanitized: *bool) LoadError(@TypeOf(reader).Error)!Root {
const lf = '\n'; const lf = '\n';
const cr = '\r'; const cr = '\r';
var buf = try self.external_allocator.alloc(u8, size); var buf = try self.external_allocator.alloc(u8, size);
@ -1117,20 +1138,69 @@ pub fn load(self: *const Self, reader: anytype, size: usize, eol_mode: *EolMode,
return Node.merge_in_place(leaves, self.allocator); return Node.merge_in_place(leaves, self.allocator);
} }
pub fn load_from_string(self: *const Self, s: []const u8, eol_mode: *EolMode, utf8_sanitized: *bool) !Root { pub const LoadFromStringError = LoadError(error{});
pub fn load_from_string(self: *const Self, s: []const u8, eol_mode: *EolMode, utf8_sanitized: *bool) LoadFromStringError!Root {
var stream = std.io.fixedBufferStream(s); var stream = std.io.fixedBufferStream(s);
return self.load(stream.reader(), s.len, eol_mode, utf8_sanitized); return self.load(stream.reader(), s.len, eol_mode, utf8_sanitized);
} }
pub fn load_from_string_and_update(self: *Self, file_path: []const u8, s: []const u8) !void { pub fn load_from_string_and_update(self: *Self, file_path: []const u8, s: []const u8) LoadFromStringError!void {
self.root = try self.load_from_string(s, &self.file_eol_mode, &self.file_utf8_sanitized); self.root = try self.load_from_string(s, &self.file_eol_mode, &self.file_utf8_sanitized);
self.file_path = try self.allocator.dupe(u8, file_path); self.file_path = try self.allocator.dupe(u8, file_path);
self.last_save = self.root; self.last_save = self.root;
self.last_save_eol_mode = self.file_eol_mode; self.last_save_eol_mode = self.file_eol_mode;
self.file_exists = false; self.file_exists = false;
self.mtime = std.time.milliTimestamp();
} }
pub fn load_from_file(self: *const Self, file_path: []const u8, file_exists: *bool, eol_mode: *EolMode, utf8_sanitized: *bool) !Root { pub const LoadFromFileError = error{
OutOfMemory,
Unexpected,
FileTooBig,
NoSpaceLeft,
DeviceBusy,
AccessDenied,
SystemResources,
WouldBlock,
IsDir,
SharingViolation,
PathAlreadyExists,
FileNotFound,
PipeBusy,
NameTooLong,
InvalidUtf8,
InvalidWtf8,
BadPathName,
NetworkNotFound,
AntivirusInterference,
SymLinkLoop,
ProcessFdQuotaExceeded,
SystemFdQuotaExceeded,
NoDevice,
NotDir,
FileLocksNotSupported,
FileBusy,
InputOutput,
BrokenPipe,
OperationAborted,
ConnectionResetByPeer,
ConnectionTimedOut,
NotOpenForReading,
SocketNotConnected,
BufferUnderrun,
DanglingSurrogateHalf,
ExpectedSecondSurrogateHalf,
UnexpectedSecondSurrogateHalf,
};
pub fn load_from_file(
self: *const Self,
file_path: []const u8,
file_exists: *bool,
eol_mode: *EolMode,
utf8_sanitized: *bool,
) LoadFromFileError!Root {
const file = cwd().openFile(file_path, .{ .mode = .read_only }) catch |e| switch (e) { const file = cwd().openFile(file_path, .{ .mode = .read_only }) catch |e| switch (e) {
error.FileNotFound => return self.new_file(file_exists), error.FileNotFound => return self.new_file(file_exists),
else => return e, else => return e,
@ -1142,7 +1212,7 @@ pub fn load_from_file(self: *const Self, file_path: []const u8, file_exists: *bo
return self.load(file.reader(), @intCast(stat.size), eol_mode, utf8_sanitized); return self.load(file.reader(), @intCast(stat.size), eol_mode, utf8_sanitized);
} }
pub fn load_from_file_and_update(self: *Self, file_path: []const u8) !void { pub fn load_from_file_and_update(self: *Self, file_path: []const u8) LoadFromFileError!void {
var file_exists: bool = false; var file_exists: bool = false;
var eol_mode: EolMode = .lf; var eol_mode: EolMode = .lf;
var utf8_sanitized: bool = false; var utf8_sanitized: bool = false;
@ -1153,6 +1223,15 @@ pub fn load_from_file_and_update(self: *Self, file_path: []const u8) !void {
self.file_eol_mode = eol_mode; self.file_eol_mode = eol_mode;
self.file_utf8_sanitized = utf8_sanitized; self.file_utf8_sanitized = utf8_sanitized;
self.last_save_eol_mode = eol_mode; self.last_save_eol_mode = eol_mode;
self.mtime = std.time.milliTimestamp();
}
pub fn reset_to_last_saved(self: *Self) void {
if (self.last_save) |last_save| {
self.store_undo(&[_]u8{}) catch {};
self.root = last_save;
self.mtime = std.time.milliTimestamp();
}
} }
pub fn store_to_string(self: *const Self, allocator: Allocator, eol_mode: EolMode) ![]u8 { pub fn store_to_string(self: *const Self, allocator: Allocator, eol_mode: EolMode) ![]u8 {
@ -1161,7 +1240,7 @@ pub fn store_to_string(self: *const Self, allocator: Allocator, eol_mode: EolMod
return s.toOwnedSlice(); return s.toOwnedSlice();
} }
fn store_to_file_const(self: *const Self, file: anytype) !void { fn store_to_file_const(self: *const Self, file: anytype) StoreToFileError!void {
const buffer_size = 4096 * 16; // 64KB const buffer_size = 4096 * 16; // 64KB
const BufferedWriter = std.io.BufferedWriter(buffer_size, std.fs.File.Writer); const BufferedWriter = std.io.BufferedWriter(buffer_size, std.fs.File.Writer);
const Writer = std.io.Writer(*BufferedWriter, BufferedWriter.Error, BufferedWriter.write); const Writer = std.io.Writer(*BufferedWriter, BufferedWriter.Error, BufferedWriter.write);
@ -1173,7 +1252,46 @@ fn store_to_file_const(self: *const Self, file: anytype) !void {
try buffered_writer.flush(); try buffered_writer.flush();
} }
pub fn store_to_existing_file_const(self: *const Self, file_path: []const u8) !void { pub const StoreToFileError = error{
AccessDenied,
AntivirusInterference,
BadPathName,
BrokenPipe,
ConnectionResetByPeer,
DeviceBusy,
DiskQuota,
FileBusy,
FileLocksNotSupported,
FileNotFound,
FileTooBig,
InputOutput,
InvalidArgument,
InvalidUtf8,
InvalidWtf8,
IsDir,
LinkQuotaExceeded,
LockViolation,
NameTooLong,
NetworkNotFound,
NoDevice,
NoSpaceLeft,
NotDir,
NotOpenForWriting,
OperationAborted,
PathAlreadyExists,
PipeBusy,
ProcessFdQuotaExceeded,
ReadOnlyFileSystem,
RenameAcrossMountPoints,
SharingViolation,
SymLinkLoop,
SystemFdQuotaExceeded,
SystemResources,
Unexpected,
WouldBlock,
};
pub fn store_to_existing_file_const(self: *const Self, file_path: []const u8) StoreToFileError!void {
const stat = try cwd().statFile(file_path); const stat = try cwd().statFile(file_path);
var atomic = try cwd().atomicFile(file_path, .{ .mode = stat.mode }); var atomic = try cwd().atomicFile(file_path, .{ .mode = stat.mode });
defer atomic.deinit(); defer atomic.deinit();
@ -1181,13 +1299,13 @@ pub fn store_to_existing_file_const(self: *const Self, file_path: []const u8) !v
try atomic.finish(); try atomic.finish();
} }
pub fn store_to_new_file_const(self: *const Self, file_path: []const u8) !void { pub fn store_to_new_file_const(self: *const Self, file_path: []const u8) StoreToFileError!void {
const file = try cwd().createFile(file_path, .{ .read = true, .truncate = true }); const file = try cwd().createFile(file_path, .{ .read = true, .truncate = true });
defer file.close(); defer file.close();
try self.store_to_file_const(file); try self.store_to_file_const(file);
} }
pub fn store_to_file_and_clean(self: *Self, file_path: []const u8) !void { pub fn store_to_file_and_clean(self: *Self, file_path: []const u8) StoreToFileError!void {
self.store_to_existing_file_const(file_path) catch |e| switch (e) { self.store_to_existing_file_const(file_path) catch |e| switch (e) {
error.FileNotFound => try self.store_to_new_file_const(file_path), error.FileNotFound => try self.store_to_new_file_const(file_path),
else => return e, else => return e,
@ -1213,6 +1331,7 @@ pub fn version(self: *const Self) usize {
pub fn update(self: *Self, root: Root) void { pub fn update(self: *Self, root: Root) void {
self.root = root; self.root = root;
self.mtime = std.time.milliTimestamp();
} }
pub fn store_undo(self: *Self, meta: []const u8) !void { pub fn store_undo(self: *Self, meta: []const u8) !void {
@ -1263,6 +1382,7 @@ pub fn undo(self: *Self, meta: []const u8) error{Stop}![]const u8 {
self.curr_history = h; self.curr_history = h;
self.root = h.root; self.root = h.root;
self.push_redo(r); self.push_redo(r);
self.mtime = std.time.milliTimestamp();
return h.meta; return h.meta;
} }
@ -1274,5 +1394,6 @@ pub fn redo(self: *Self) error{Stop}![]const u8 {
self.curr_history = h; self.curr_history = h;
self.root = h.root; self.root = h.root;
self.push_undo(u); self.push_undo(u);
self.mtime = std.time.milliTimestamp();
return h.meta; return h.meta;
} }

87
src/buffer/Manager.zig Normal file
View file

@ -0,0 +1,87 @@
const std = @import("std");
const Buffer = @import("Buffer.zig");
const Self = @This();
allocator: std.mem.Allocator,
buffers: std.StringHashMapUnmanaged(*Buffer),
pub fn init(allocator: std.mem.Allocator) Self {
return .{
.allocator = allocator,
.buffers = .{},
};
}
pub fn deinit(self: *Self) void {
var i = self.buffers.iterator();
while (i.next()) |p| {
self.allocator.free(p.key_ptr.*);
p.value_ptr.*.deinit();
}
self.buffers.deinit(self.allocator);
}
pub fn open_file(self: *Self, file_path: []const u8) Buffer.LoadFromFileError!*Buffer {
const buffer = if (self.buffers.get(file_path)) |buffer| buffer else blk: {
var buffer = try Buffer.create(self.allocator);
errdefer buffer.deinit();
try buffer.load_from_file_and_update(file_path);
try self.buffers.put(self.allocator, try self.allocator.dupe(u8, file_path), buffer);
break :blk buffer;
};
buffer.update_last_used_time();
return buffer;
}
pub fn open_scratch(self: *Self, file_path: []const u8, content: []const u8) Buffer.LoadFromStringError!*Buffer {
const buffer = if (self.buffers.get(file_path)) |buffer| buffer else blk: {
var buffer = try Buffer.create(self.allocator);
errdefer buffer.deinit();
try buffer.load_from_string_and_update(file_path, content);
buffer.file_exists = true;
try self.buffers.put(self.allocator, try self.allocator.dupe(u8, file_path), buffer);
break :blk buffer;
};
buffer.update_last_used_time();
return buffer;
}
pub fn retire(_: *Self, buffer: *Buffer) void {
buffer.update_last_used_time();
}
pub fn list_most_recently_used(self: *Self, allocator: std.mem.Allocator) error{OutOfMemory}![]*Buffer {
var buffers: std.ArrayListUnmanaged(*Buffer) = .{};
var i = self.buffers.iterator();
while (i.next()) |kv|
(try buffers.addOne(allocator)).* = kv.value_ptr.*;
std.mem.sort(*Buffer, buffers.items, {}, struct {
fn less_fn(_: void, lhs: *Buffer, rhs: *Buffer) bool {
return lhs.utime > rhs.utime;
}
}.less_fn);
return buffers.toOwnedSlice(allocator);
}
pub fn is_dirty(self: *const Self) bool {
var i = self.buffers.iterator();
while (i.next()) |kv|
if (kv.value_ptr.*.is_dirty())
return true;
return false;
}
pub fn is_buffer_dirty(self: *const Self, file_path: []const u8) bool {
return if (self.buffers.get(file_path)) |buffer| buffer.is_dirty() else false;
}
pub fn save_all(self: *const Self) Buffer.StoreToFileError!void {
var i = self.buffers.iterator();
while (i.next()) |kv| {
const buffer = kv.value_ptr.*;
try buffer.store_to_file_and_clean(buffer.file_path);
}
}

View file

@ -1,6 +1,7 @@
{ {
"project": { "project": {
"press": [ "press": [
["ctrl+shift+e", "switch_buffers"],
["ctrl+0", "reset_fontsize"], ["ctrl+0", "reset_fontsize"],
["ctrl+plus", "adjust_fontsize", 1.0], ["ctrl+plus", "adjust_fontsize", 1.0],
["ctrl+minus", "adjust_fontsize", -1.0], ["ctrl+minus", "adjust_fontsize", -1.0],
@ -250,6 +251,7 @@
["ctrl+p", "palette_menu_up"], ["ctrl+p", "palette_menu_up"],
["ctrl+n", "palette_menu_down"], ["ctrl+n", "palette_menu_down"],
["ctrl+e", "palette_menu_down"], ["ctrl+e", "palette_menu_down"],
["ctrl+shift+e", "palette_menu_down"],
["ctrl+r", "palette_menu_down"], ["ctrl+r", "palette_menu_down"],
["ctrl+t", "palette_menu_down"], ["ctrl+t", "palette_menu_down"],
["ctrl+v", "system_paste"], ["ctrl+v", "system_paste"],

View file

@ -19,6 +19,8 @@ const gui = @import("gui");
const DropWriter = gui.DropWriter; const DropWriter = gui.DropWriter;
pub const style = StyleBits; pub const style = StyleBits;
pub const panic = win32.messageBoxThenPanic(.{ .title = "Flow Panic" });
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
vx: vaxis.Vaxis, vx: vaxis.Vaxis,
@ -84,25 +86,6 @@ pub fn deinit(self: *Self) void {
self.title_buf.deinit(); self.title_buf.deinit();
} }
threadlocal var thread_is_panicing = false;
pub fn panic(
msg: []const u8,
error_return_trace: ?*std.builtin.StackTrace,
ret_addr: ?usize,
) noreturn {
if (!thread_is_panicing) {
thread_is_panicing = true;
const msg_z: [:0]const u8 = if (std.fmt.allocPrintZ(
std.heap.page_allocator,
"{s}",
.{msg},
)) |msg_z| msg_z else |_| "failed allocate error message";
_ = win32.MessageBoxA(null, msg_z, "Flow Panic", .{ .ICONASTERISK = 1 });
}
std.builtin.default_panic(msg, error_return_trace, ret_addr);
}
pub fn run(self: *Self) !void { pub fn run(self: *Self) !void {
if (self.thread) |_| return; if (self.thread) |_| return;
@ -353,7 +336,7 @@ fn update_window_title(self: *Self) void {
const title = self.title_buf.toOwnedSliceSentinel(0) catch @panic("OOM:update_window_title"); const title = self.title_buf.toOwnedSliceSentinel(0) catch @panic("OOM:update_window_title");
if (win32.SetWindowTextW(hwnd, title) == 0) { if (win32.SetWindowTextW(hwnd, title) == 0) {
std.log.warn("SetWindowText failed with {}", .{win32.GetLastError().fmt()}); std.log.warn("SetWindowText failed, error={}", .{win32.GetLastError()});
self.title_buf = std.ArrayList(u16).fromOwnedSlice(self.allocator, title); self.title_buf = std.ArrayList(u16).fromOwnedSlice(self.allocator, title);
} else { } else {
self.allocator.free(title); self.allocator.free(title);

View file

@ -242,6 +242,7 @@ pub const Editor = struct {
file_path: ?[]const u8, file_path: ?[]const u8,
buffer: ?*Buffer, buffer: ?*Buffer,
buffer_manager: *Buffer.Manager,
lsp_version: usize = 1, lsp_version: usize = 1,
pause_undo: bool = false, pause_undo: bool = false,
@ -382,7 +383,7 @@ pub const Editor = struct {
self.clamp(); self.clamp();
} }
fn init(self: *Self, allocator: Allocator, n: Plane) void { fn init(self: *Self, allocator: Allocator, n: Plane, buffer_manager: *Buffer.Manager) void {
const logger = log.logger("editor"); const logger = log.logger("editor");
const frame_rate = tp.env.get().num("frame-rate"); const frame_rate = tp.env.get().num("frame-rate");
const indent_size = tui.current().config.indent_size; const indent_size = tui.current().config.indent_size;
@ -396,6 +397,7 @@ pub const Editor = struct {
.logger = logger, .logger = logger,
.file_path = null, .file_path = null,
.buffer = null, .buffer = null,
.buffer_manager = buffer_manager,
.handlers = EventHandler.List.init(allocator), .handlers = EventHandler.List.init(allocator),
.animation_lag = get_animation_max_lag(), .animation_lag = get_animation_max_lag(),
.animation_frame_rate = frame_rate, .animation_frame_rate = frame_rate,
@ -417,7 +419,7 @@ pub const Editor = struct {
self.matches.deinit(); self.matches.deinit();
self.handlers.deinit(); self.handlers.deinit();
self.logger.deinit(); self.logger.deinit();
if (self.buffer) |p| p.deinit(); if (self.buffer) |p| self.buffer_manager.retire(p);
if (self.case_data) |cd| cd.deinit(); if (self.case_data) |cd| cd.deinit();
} }
@ -483,28 +485,16 @@ pub const Editor = struct {
self.view.cols = pos.w; self.view.cols = pos.w;
} }
pub fn is_dirty(self: *Self) bool {
const b = self.buffer orelse return false;
return b.is_dirty();
}
fn open(self: *Self, file_path: []const u8) !void { fn open(self: *Self, file_path: []const u8) !void {
var new_buf = try Buffer.create(self.allocator); return self.open_buffer(file_path, try self.buffer_manager.open_file(file_path));
errdefer new_buf.deinit();
try new_buf.load_from_file_and_update(file_path);
return self.open_buffer(file_path, new_buf);
} }
fn open_scratch(self: *Self, file_path: []const u8, content: []const u8) !void { fn open_scratch(self: *Self, file_path: []const u8, content: []const u8) !void {
var new_buf = try Buffer.create(self.allocator); return self.open_buffer(file_path, try self.buffer_manager.open_scratch(file_path, content));
errdefer new_buf.deinit();
try new_buf.load_from_string_and_update(file_path, content);
new_buf.file_exists = true;
return self.open_buffer(file_path, new_buf);
} }
fn open_buffer(self: *Self, file_path: []const u8, new_buf: *Buffer) !void { fn open_buffer(self: *Self, file_path: []const u8, new_buf: *Buffer) !void {
errdefer new_buf.deinit(); errdefer self.buffer_manager.retire(new_buf);
self.cancel_all_selections(); self.cancel_all_selections();
self.get_primary().reset(); self.get_primary().reset();
self.file_path = try self.allocator.dupe(u8, file_path); self.file_path = try self.allocator.dupe(u8, file_path);
@ -543,17 +533,7 @@ pub const Editor = struct {
} }
fn close(self: *Self) !void { fn close(self: *Self) !void {
return self.close_internal(false); if (self.buffer) |b_mut| self.buffer_manager.retire(b_mut);
}
fn close_dirty(self: *Self) !void {
return self.close_internal(true);
}
fn close_internal(self: *Self, allow_dirty_close: bool) !void {
const b = self.buffer orelse return error.Stop;
if (!allow_dirty_close and b.is_dirty()) return tp.exit("unsaved changes");
if (self.buffer) |b_mut| b_mut.deinit();
self.buffer = null; self.buffer = null;
self.plane.erase(); self.plane.erase();
self.plane.home(); self.plane.home();
@ -3805,7 +3785,8 @@ pub const Editor = struct {
pub fn close_file_without_saving(self: *Self, _: Context) Result { pub fn close_file_without_saving(self: *Self, _: Context) Result {
self.cancel_all_selections(); self.cancel_all_selections();
try self.close_dirty(); if (self.buffer) |buffer| buffer.reset_to_last_saved();
try self.close();
} }
pub const close_file_without_saving_meta = .{ .description = "Close file without saving" }; pub const close_file_without_saving_meta = .{ .description = "Close file without saving" };
@ -4794,8 +4775,8 @@ pub const Editor = struct {
pub const set_file_type_meta = .{ .arguments = &.{.string} }; pub const set_file_type_meta = .{ .arguments = &.{.string} };
}; };
pub fn create(allocator: Allocator, parent: Widget) !Widget { pub fn create(allocator: Allocator, parent: Widget, buffer_manager: *Buffer.Manager) !Widget {
return EditorWidget.create(allocator, parent); return EditorWidget.create(allocator, parent, buffer_manager);
} }
pub const EditorWidget = struct { pub const EditorWidget = struct {
@ -4817,10 +4798,10 @@ pub const EditorWidget = struct {
const Self = @This(); const Self = @This();
const Commands = command.Collection(Editor); const Commands = command.Collection(Editor);
fn create(allocator: Allocator, parent: Widget) !Widget { fn create(allocator: Allocator, parent: Widget, buffer_manager: *Buffer.Manager) !Widget {
const container = try WidgetList.createH(allocator, parent, "editor.container", .dynamic); const container = try WidgetList.createH(allocator, parent, "editor.container", .dynamic);
const self: *Self = try allocator.create(Self); const self: *Self = try allocator.create(Self);
try self.init(allocator, container.widget()); try self.init(allocator, container.widget(), buffer_manager);
try self.commands.init(&self.editor); try self.commands.init(&self.editor);
const editorWidget = Widget.to(self); const editorWidget = Widget.to(self);
try container.add(try editor_gutter.create(allocator, container.widget(), editorWidget, &self.editor)); try container.add(try editor_gutter.create(allocator, container.widget(), editorWidget, &self.editor));
@ -4829,7 +4810,7 @@ pub const EditorWidget = struct {
return container.widget(); return container.widget();
} }
fn init(self: *Self, allocator: Allocator, parent: Widget) !void { fn init(self: *Self, allocator: Allocator, parent: Widget, buffer_manager: *Buffer.Manager) !void {
var n = try Plane.init(&(Widget.Box{}).opts("editor"), parent.plane.*); var n = try Plane.init(&(Widget.Box{}).opts("editor"), parent.plane.*);
errdefer n.deinit(); errdefer n.deinit();
@ -4838,7 +4819,7 @@ pub const EditorWidget = struct {
.plane = n, .plane = n,
.editor = undefined, .editor = undefined,
}; };
self.editor.init(allocator, n); self.editor.init(allocator, n, buffer_manager);
errdefer self.editor.deinit(); errdefer self.editor.deinit();
try self.editor.push_cursor(); try self.editor.push_cursor();
} }

View file

@ -245,6 +245,12 @@ const cmds = struct {
const Ctx = command.Context; const Ctx = command.Context;
const Result = command.Result; const Result = command.Result;
pub fn save_all(_: *Self, _: Ctx) Result {
if (tui.get_buffer_manager()) |bm|
bm.save_all() catch |e| return tp.exit_error(e, @errorReturnTrace());
}
pub const save_all_meta = .{ .description = "Save all changed files" };
pub fn home_menu_down(self: *Self, _: Ctx) Result { pub fn home_menu_down(self: *Self, _: Ctx) Result {
self.menu.select_down(); self.menu.select_down();
} }

View file

@ -14,6 +14,7 @@ const build_options = @import("build_options");
const Plane = @import("renderer").Plane; const Plane = @import("renderer").Plane;
const input = @import("input"); const input = @import("input");
const command = @import("command"); const command = @import("command");
const BufferManager = @import("Buffer").Manager;
const tui = @import("tui.zig"); const tui = @import("tui.zig");
const Box = @import("Box.zig"); const Box = @import("Box.zig");
@ -47,6 +48,7 @@ active_view: ?usize = 0,
panels: ?*WidgetList = null, panels: ?*WidgetList = null,
last_match_text: ?[]const u8 = null, last_match_text: ?[]const u8 = null,
location_history: location_history, location_history: location_history,
buffer_manager: BufferManager,
file_stack: std.ArrayList([]const u8), file_stack: std.ArrayList([]const u8),
find_in_files_state: enum { init, adding, done } = .done, find_in_files_state: enum { init, adding, done } = .done,
file_list_type: FileListType = .find_in_files, file_list_type: FileListType = .find_in_files,
@ -70,6 +72,7 @@ pub fn create(allocator: std.mem.Allocator) !Widget {
.file_stack = std.ArrayList([]const u8).init(allocator), .file_stack = std.ArrayList([]const u8).init(allocator),
.views = undefined, .views = undefined,
.views_widget = undefined, .views_widget = undefined,
.buffer_manager = BufferManager.init(allocator),
}; };
try self.commands.init(self); try self.commands.init(self);
const w = Widget.to(self); const w = Widget.to(self);
@ -104,6 +107,7 @@ pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
self.commands.deinit(); self.commands.deinit();
self.widgets.deinit(allocator); self.widgets.deinit(allocator);
self.floating_views.deinit(); self.floating_views.deinit();
self.buffer_manager.deinit();
allocator.destroy(self); allocator.destroy(self);
} }
@ -237,15 +241,8 @@ fn toggle_view(self: *Self, view: anytype) !void {
} }
fn check_all_not_dirty(self: *const Self) command.Result { fn check_all_not_dirty(self: *const Self) command.Result {
for (self.editors.items) |editor| if (self.buffer_manager.is_dirty())
if (editor.is_dirty()) return tp.exit("unsaved changes");
return tp.exit("unsaved changes");
}
fn check_active_not_dirty(self: *const Self) command.Result {
if (self.active_editor) |idx|
if (self.editors.items[idx].is_dirty())
return tp.exit("unsaved changes");
} }
const cmds = struct { const cmds = struct {
@ -296,6 +293,8 @@ const cmds = struct {
self.clear_find_in_files_results(.diagnostics); self.clear_find_in_files_results(.diagnostics);
if (self.file_list_type == .diagnostics and self.is_panel_view_showing(filelist_view)) if (self.file_list_type == .diagnostics and self.is_panel_view_showing(filelist_view))
try self.toggle_panel_view(filelist_view, false); try self.toggle_panel_view(filelist_view, false);
self.buffer_manager.deinit();
self.buffer_manager = BufferManager.init(self.allocator);
try project_manager.open(project_dir); try project_manager.open(project_dir);
const project = tp.env.get().str("project"); const project = tp.env.get().str("project");
tui.current().rdr.set_terminal_working_directory(project); tui.current().rdr.set_terminal_working_directory(project);
@ -352,7 +351,6 @@ const cmds = struct {
if (!same_file) { if (!same_file) {
if (self.get_active_editor()) |editor| { if (self.get_active_editor()) |editor| {
try self.check_active_not_dirty();
editor.send_editor_jump_source() catch {}; editor.send_editor_jump_source() catch {};
} }
try self.create_editor(); try self.create_editor();
@ -395,7 +393,6 @@ const cmds = struct {
pub const open_gui_config_meta = .{ .description = "Edit gui configuration file" }; pub const open_gui_config_meta = .{ .description = "Edit gui configuration file" };
pub fn create_scratch_buffer(self: *Self, ctx: Ctx) Result { pub fn create_scratch_buffer(self: *Self, ctx: Ctx) Result {
try self.check_all_not_dirty();
tui.reset_drag_context(); tui.reset_drag_context();
try self.create_editor(); try self.create_editor();
try command.executeName("open_scratch_buffer", ctx); try command.executeName("open_scratch_buffer", ctx);
@ -863,7 +860,7 @@ fn create_editor(self: *Self) !void {
if (self.get_active_file_path()) |file_path| self.push_file_stack(file_path) catch {}; if (self.get_active_file_path()) |file_path| self.push_file_stack(file_path) catch {};
try self.delete_active_view(); try self.delete_active_view();
command.executeName("enter_mode_default", .{}) catch {}; command.executeName("enter_mode_default", .{}) catch {};
var editor_widget = try ed.create(self.allocator, Widget.to(self)); var editor_widget = try ed.create(self.allocator, Widget.to(self), &self.buffer_manager);
errdefer editor_widget.deinit(self.allocator); errdefer editor_widget.deinit(self.allocator);
const editor = editor_widget.get("editor") orelse @panic("mainview editor not found"); const editor = editor_widget.get("editor") orelse @panic("mainview editor not found");
if (self.top_bar) |bar| editor.subscribe(EventHandler.to_unowned(bar)) catch @panic("subscribe unsupported"); if (self.top_bar) |bar| editor.subscribe(EventHandler.to_unowned(bar)) catch @panic("subscribe unsupported");

View file

@ -11,7 +11,6 @@ pub const create = Type.create;
pub fn load_entries(self: *Type) error{ Exit, OutOfMemory }!void { pub fn load_entries(self: *Type) error{ Exit, OutOfMemory }!void {
const editor = tui.get_active_editor() orelse return; const editor = tui.get_active_editor() orelse return;
if (editor.is_dirty()) return tp.exit("unsaved changes");
if (editor.file_path) |old_path| if (editor.file_path) |old_path|
if (std.mem.lastIndexOf(u8, old_path, "/")) |pos| if (std.mem.lastIndexOf(u8, old_path, "/")) |pos|
try self.file_path.appendSlice(old_path[0 .. pos + 1]); try self.file_path.appendSlice(old_path[0 .. pos + 1]);

View file

@ -0,0 +1,49 @@
const std = @import("std");
const cbor = @import("cbor");
const tp = @import("thespian");
const root = @import("root");
const command = @import("command");
const tui = @import("../../tui.zig");
pub const Type = @import("palette.zig").Create(@This());
pub const label = "Switch buffers";
pub const name = " buffer";
pub const description = "buffer";
const dirty_indicator = "";
pub const Entry = struct {
label: []const u8,
hint: []const u8,
};
pub fn load_entries(palette: *Type) !usize {
const buffer_manager = tui.get_buffer_manager() orelse return 0;
const buffers = try buffer_manager.list_most_recently_used(palette.allocator);
defer palette.allocator.free(buffers);
for (buffers) |buffer| {
const hint = if (buffer.is_dirty()) dirty_indicator else "";
(try palette.entries.addOne()).* = .{ .label = buffer.file_path, .hint = hint };
}
return if (palette.entries.items.len == 0) label.len else 2;
}
pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !void {
var value = std.ArrayList(u8).init(palette.allocator);
defer value.deinit();
const writer = value.writer();
try cbor.writeValue(writer, entry.label);
try cbor.writeValue(writer, entry.hint);
try cbor.writeValue(writer, matches orelse &[_]usize{});
try palette.menu.add_item_with_handler(value.items, select);
palette.items += 1;
}
fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
var file_path: []const u8 = undefined;
var iter = button.opts.label;
if (!(cbor.matchString(&iter, &file_path) catch false)) return;
tp.self_pid().send(.{ "cmd", "exit_overlay_mode" }) catch |e| menu.*.opts.ctx.logger.err("navigate", e);
tp.self_pid().send(.{ "cmd", "navigate", .{} }) catch |e| menu.*.opts.ctx.logger.err("navigate", e);
tp.self_pid().send(.{ "cmd", "navigate", .{ .file = file_path } }) catch |e| menu.*.opts.ctx.logger.err("navigate", e);
}

View file

@ -10,6 +10,7 @@ const keybind = @import("keybind");
const project_manager = @import("project_manager"); const project_manager = @import("project_manager");
const command = @import("command"); const command = @import("command");
const EventHandler = @import("EventHandler"); const EventHandler = @import("EventHandler");
const BufferManager = @import("Buffer").Manager;
const tui = @import("../../tui.zig"); const tui = @import("../../tui.zig");
const MessageFilter = @import("../../MessageFilter.zig"); const MessageFilter = @import("../../MessageFilter.zig");
@ -34,6 +35,7 @@ need_reset: bool = false,
need_select_first: bool = true, need_select_first: bool = true,
longest: usize = 0, longest: usize = 0,
commands: Commands = undefined, commands: Commands = undefined,
buffer_manager: ?*BufferManager,
pub fn create(allocator: std.mem.Allocator) !tui.Mode { pub fn create(allocator: std.mem.Allocator) !tui.Mode {
const mv = tui.current().mainview.dynamic_cast(mainview) orelse return error.NotFound; const mv = tui.current().mainview.dynamic_cast(mainview) orelse return error.NotFound;
@ -51,6 +53,7 @@ pub fn create(allocator: std.mem.Allocator) !tui.Mode {
.ctx = self, .ctx = self,
.label = "Search files by name", .label = "Search files by name",
}))).dynamic_cast(InputBox.State(*Self)) orelse unreachable, }))).dynamic_cast(InputBox.State(*Self)) orelse unreachable,
.buffer_manager = tui.get_buffer_manager(),
}; };
try self.commands.init(self); try self.commands.init(self);
try tui.current().message_filters.add(MessageFilter.bind(self, receive_project_manager)); try tui.current().message_filters.add(MessageFilter.bind(self, receive_project_manager));
@ -93,7 +96,7 @@ inline fn max_menu_width() usize {
return @max(15, width - (width / 5)); return @max(15, width - (width / 5));
} }
fn on_render_menu(_: *Self, button: *Button.State(*Menu.State(*Self)), theme: *const Widget.Theme, selected: bool) bool { fn on_render_menu(self: *Self, button: *Button.State(*Menu.State(*Self)), theme: *const Widget.Theme, selected: bool) bool {
const style_base = theme.editor_widget; const style_base = theme.editor_widget;
const style_label = if (button.active) theme.editor_cursor else if (button.hover or selected) theme.editor_selection else theme.editor_widget; const style_label = if (button.active) theme.editor_cursor else if (button.hover or selected) theme.editor_selection else theme.editor_widget;
const style_keybind = if (tui.find_scope_style(theme, "entity.name")) |sty| sty.style else style_base; const style_keybind = if (tui.find_scope_style(theme, "entity.name")) |sty| sty.style else style_base;
@ -110,7 +113,8 @@ fn on_render_menu(_: *Self, button: *Button.State(*Menu.State(*Self)), theme: *c
if (!(cbor.matchString(&iter, &file_path) catch false)) if (!(cbor.matchString(&iter, &file_path) catch false))
file_path = "#ERROR#"; file_path = "#ERROR#";
button.plane.set_style(style_keybind); button.plane.set_style(style_keybind);
const pointer = if (selected) "" else " "; const dirty = if (self.buffer_manager) |bm| if (bm.is_buffer_dirty(file_path)) "" else " " else " ";
const pointer = if (selected) "" else dirty;
_ = button.plane.print("{s}", .{pointer}) catch {}; _ = button.plane.print("{s}", .{pointer}) catch {};
var buf: [std.fs.max_path_bytes]u8 = undefined; var buf: [std.fs.max_path_bytes]u8 = undefined;
var removed_prefix: usize = 0; var removed_prefix: usize = 0;

View file

@ -56,6 +56,7 @@ mouse_idle_timer: ?tp.Cancellable = null,
default_cursor: keybind.CursorShape = .default, default_cursor: keybind.CursorShape = .default,
fontface: []const u8 = "", fontface: []const u8 = "",
fontfaces: ?std.ArrayList([]const u8) = null, fontfaces: ?std.ArrayList([]const u8) = null,
enable_mouse_idle_timer: bool = false,
const keepalive = std.time.us_per_day * 365; // one year const keepalive = std.time.us_per_day * 365; // one year
const idle_frames = 0; const idle_frames = 0;
@ -222,6 +223,7 @@ fn listen_sigwinch(self: *Self) tp.result {
} }
fn update_mouse_idle_timer(self: *Self) void { fn update_mouse_idle_timer(self: *Self) void {
if (!self.enable_mouse_idle_timer) return;
const delay = std.time.us_per_ms * @as(u64, mouse_idle_time_milliseconds); const delay = std.time.us_per_ms * @as(u64, mouse_idle_time_milliseconds);
if (self.mouse_idle_timer) |*t| { if (self.mouse_idle_timer) |*t| {
t.cancel() catch {}; t.cancel() catch {};
@ -309,6 +311,8 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) !void {
if (try m.match(.{"resize"})) { if (try m.match(.{"resize"})) {
self.resize(); self.resize();
const box = self.screen();
message("{d}x{d}", .{ box.w, box.h });
return; return;
} }
@ -811,6 +815,11 @@ const cmds = struct {
} }
pub const open_recent_project_meta = .{ .description = "Open recent project" }; pub const open_recent_project_meta = .{ .description = "Open recent project" };
pub fn switch_buffers(self: *Self, _: Ctx) Result {
return self.enter_overlay_mode(@import("mode/overlay/buffer_palette.zig").Type);
}
pub const switch_buffers_meta = .{ .description = "Switch buffers" };
pub fn change_theme(self: *Self, _: Ctx) Result { pub fn change_theme(self: *Self, _: Ctx) Result {
return self.enter_overlay_mode(@import("mode/overlay/theme_palette.zig").Type); return self.enter_overlay_mode(@import("mode/overlay/theme_palette.zig").Type);
} }
@ -987,6 +996,10 @@ pub fn get_active_selection(allocator: std.mem.Allocator) ?[]u8 {
return editor.get_selection(sel, allocator) catch null; return editor.get_selection(sel, allocator) catch null;
} }
pub fn get_buffer_manager() ?*@import("Buffer").Manager {
return if (current().mainview.dynamic_cast(mainview)) |mv_| &mv_.buffer_manager else null;
}
fn context_check() void { fn context_check() void {
if (instance_ == null) @panic("tui call out of context"); if (instance_ == null) @panic("tui call out of context");
} }
@ -1176,3 +1189,8 @@ pub fn is_cursor_beam(self: *Self) bool {
pub fn get_selection_style(self: *Self) @import("Buffer").Selection.Style { pub fn get_selection_style(self: *Self) @import("Buffer").Selection.Style {
return if (self.input_mode) |mode| mode.selection_style else .normal; return if (self.input_mode) |mode| mode.selection_style else .normal;
} }
pub fn message(comptime fmt: anytype, args: anytype) void {
var buf: [256]u8 = undefined;
tp.self_pid().send(.{ "message", std.fmt.bufPrint(&buf, fmt, args) catch @panic("too large") }) catch {};
}

View file

@ -309,7 +309,7 @@ pub fn paint(
for (0..shader_row_count) |row| { for (0..shader_row_count) |row| {
const src_row = blk: { const src_row = blk: {
const r = top + row; const r = top + row;
break :blk r - if (r >= row_count) row_count else 0; break :blk if (r < row_count) r else 0;
}; };
const src_row_offset = src_row * col_count; const src_row_offset = src_row * col_count;
const dst_row_offset = row * shader_col_count; const dst_row_offset = row * shader_col_count;

View file

@ -1,4 +1,5 @@
const std = @import("std"); const std = @import("std");
const tracy = @import("tracy");
const build_options = @import("build_options"); const build_options = @import("build_options");
const root = @import("root"); const root = @import("root");
@ -92,6 +93,8 @@ const window_style_ex = win32.WINDOW_EX_STYLE{
const window_style = win32.WS_OVERLAPPEDWINDOW; const window_style = win32.WS_OVERLAPPEDWINDOW;
pub fn init() void { pub fn init() void {
const frame = tracy.initZone(@src(), .{ .name = "gui init" });
defer frame.deinit();
std.debug.assert(!global.init_called); std.debug.assert(!global.init_called);
global.init_called = true; global.init_called = true;
render.init(.{}); render.init(.{});
@ -102,6 +105,8 @@ const Icons = struct {
large: win32.HICON, large: win32.HICON,
}; };
fn getIcons(dpi: XY(u32)) Icons { fn getIcons(dpi: XY(u32)) Icons {
const frame = tracy.initZone(@src(), .{ .name = "gui getIcons" });
defer frame.deinit();
const small_x = win32.GetSystemMetricsForDpi(@intFromEnum(win32.SM_CXSMICON), dpi.x); const small_x = win32.GetSystemMetricsForDpi(@intFromEnum(win32.SM_CXSMICON), dpi.x);
const small_y = win32.GetSystemMetricsForDpi(@intFromEnum(win32.SM_CYSMICON), dpi.y); const small_y = win32.GetSystemMetricsForDpi(@intFromEnum(win32.SM_CYSMICON), dpi.y);
const large_x = win32.GetSystemMetricsForDpi(@intFromEnum(win32.SM_CXICON), dpi.x); const large_x = win32.GetSystemMetricsForDpi(@intFromEnum(win32.SM_CXICON), dpi.x);
@ -118,7 +123,7 @@ fn getIcons(dpi: XY(u32)) Icons {
small_x, small_x,
small_y, small_y,
win32.LR_SHARED, win32.LR_SHARED,
) orelse fatalWin32("LoadImage for small icon", win32.GetLastError()); ) orelse win32.panicWin32("LoadImage for small icon", win32.GetLastError());
const large = win32.LoadImageW( const large = win32.LoadImageW(
win32.GetModuleHandleW(null), win32.GetModuleHandleW(null),
@ptrFromInt(c.ID_ICON_FLOW), @ptrFromInt(c.ID_ICON_FLOW),
@ -126,7 +131,7 @@ fn getIcons(dpi: XY(u32)) Icons {
large_x, large_x,
large_y, large_y,
win32.LR_SHARED, win32.LR_SHARED,
) orelse fatalWin32("LoadImage for large icon", win32.GetLastError()); ) orelse win32.panicWin32("LoadImage for large icon", win32.GetLastError());
return .{ .small = @ptrCast(small), .large = @ptrCast(large) }; return .{ .small = @ptrCast(small), .large = @ptrCast(large) };
} }
@ -190,6 +195,8 @@ fn getFontSize() f32 {
} }
fn getFont(dpi: u32, size: f32, face: *const FontFace) render.Font { fn getFont(dpi: u32, size: f32, face: *const FontFace) render.Font {
const frame = tracy.initZone(@src(), .{ .name = "gui getFont" });
defer frame.deinit();
if (global.font) |*font| { if (global.font) |*font| {
if (font.dpi == dpi and font.size == size and font.face.eql(face)) if (font.dpi == dpi and font.size == size and font.face.eql(face))
return font.render_object; return font.render_object;
@ -251,6 +258,8 @@ fn calcWindowPlacement(
initial_window_x: u16, initial_window_x: u16,
initial_window_y: u16, initial_window_y: u16,
) WindowPlacement { ) WindowPlacement {
const frame = tracy.initZone(@src(), .{ .name = "gui calcWindowPlacement" });
defer frame.deinit();
var result = WindowPlacement.default; var result = WindowPlacement.default;
const monitor = maybe_monitor orelse return result; const monitor = maybe_monitor orelse return result;
@ -259,7 +268,7 @@ fn calcWindowPlacement(
var info: win32.MONITORINFO = undefined; var info: win32.MONITORINFO = undefined;
info.cbSize = @sizeOf(win32.MONITORINFO); info.cbSize = @sizeOf(win32.MONITORINFO);
if (0 == win32.GetMonitorInfoW(monitor, &info)) { if (0 == win32.GetMonitorInfoW(monitor, &info)) {
std.log.warn("GetMonitorInfo failed with {}", .{win32.GetLastError().fmt()}); std.log.warn("GetMonitorInfo failed, error={}", .{win32.GetLastError()});
return result; return result;
} }
break :blk info.rcWork; break :blk info.rcWork;
@ -326,7 +335,7 @@ fn entry(pid: thespian.pid) !void {
}, },
win32.MONITOR_DEFAULTTOPRIMARY, win32.MONITOR_DEFAULTTOPRIMARY,
) orelse { ) orelse {
std.log.warn("MonitorFromPoint failed with {}", .{win32.GetLastError().fmt()}); std.log.warn("MonitorFromPoint failed, error={}", .{win32.GetLastError()});
break :blk null; break :blk null;
}; };
}; };
@ -375,7 +384,7 @@ fn entry(pid: thespian.pid) !void {
.lpszClassName = CLASS_NAME, .lpszClassName = CLASS_NAME,
.hIconSm = global.icons.small, .hIconSm = global.icons.small,
}; };
if (0 == win32.RegisterClassExW(&wc)) fatalWin32( if (0 == win32.RegisterClassExW(&wc)) win32.panicWin32(
"RegisterClass for main window", "RegisterClass for main window",
win32.GetLastError(), win32.GetLastError(),
); );
@ -394,7 +403,7 @@ fn entry(pid: thespian.pid) !void {
null, // Menu null, // Menu
win32.GetModuleHandleW(null), win32.GetModuleHandleW(null),
@ptrCast(&create_args), @ptrCast(&create_args),
) orelse fatalWin32("CreateWindow", win32.GetLastError()); ) orelse win32.panicWin32("CreateWindow", win32.GetLastError());
// NEVER DESTROY THE WINDOW! // NEVER DESTROY THE WINDOW!
// This allows us to send the hwnd to other thread/parts // This allows us to send the hwnd to other thread/parts
// of the app and it will always be valid. // of the app and it will always be valid.
@ -421,7 +430,7 @@ fn entry(pid: thespian.pid) !void {
); );
} }
if (0 == win32.UpdateWindow(hwnd)) fatalWin32("UpdateWindow", win32.GetLastError()); if (0 == win32.UpdateWindow(hwnd)) win32.panicWin32("UpdateWindow", win32.GetLastError());
_ = win32.ShowWindow(hwnd, win32.SW_SHOWNORMAL); _ = win32.ShowWindow(hwnd, win32.SW_SHOWNORMAL);
// try some things to bring our window to the top // try some things to bring our window to the top
@ -531,12 +540,14 @@ fn updateWindowSize(
edge: ?win32.WPARAM, edge: ?win32.WPARAM,
bounds_ref: *?WindowBounds, bounds_ref: *?WindowBounds,
) void { ) void {
const frame = tracy.initZone(@src(), .{ .name = "gui updateWindowSize" });
defer frame.deinit();
const dpi = win32.dpiFromHwnd(hwnd); const dpi = win32.dpiFromHwnd(hwnd);
const font = getFont(dpi, getFontSize(), getFontFace()); const font = getFont(dpi, getFontSize(), getFontFace());
const cell_size = font.getCellSize(i32); const cell_size = font.getCellSize(i32);
var window_rect: win32.RECT = undefined; var window_rect: win32.RECT = undefined;
if (0 == win32.GetWindowRect(hwnd, &window_rect)) fatalWin32( if (0 == win32.GetWindowRect(hwnd, &window_rect)) win32.panicWin32(
"GetWindowRect", "GetWindowRect",
win32.GetLastError(), win32.GetLastError(),
); );
@ -564,6 +575,8 @@ fn updateWindowSize(
} }
fn getFontFaces(state: *State) void { fn getFontFaces(state: *State) void {
const frame = tracy.initZone(@src(), .{ .name = "gui getFontFaces" });
defer frame.deinit();
const fonts = render.Fonts.init(); const fonts = render.Fonts.init();
defer fonts.deinit(); defer fonts.deinit();
var buf: [FontFace.max * 2]u8 = undefined; var buf: [FontFace.max * 2]u8 = undefined;
@ -631,7 +644,9 @@ fn sendMouse(
wparam: win32.WPARAM, wparam: win32.WPARAM,
lparam: win32.LPARAM, lparam: win32.LPARAM,
) void { ) void {
const point = win32ext.pointFromLparam(lparam); const frame = tracy.initZone(@src(), .{ .name = "gui sendMouse" });
defer frame.deinit();
const point = win32.pointFromLparam(lparam);
const state = stateFromHwnd(hwnd); const state = stateFromHwnd(hwnd);
const dpi = win32.dpiFromHwnd(hwnd); const dpi = win32.dpiFromHwnd(hwnd);
const cell_size = getFont(dpi, getFontSize(), getFontFace()).getCellSize(i32); const cell_size = getFont(dpi, getFontSize(), getFontFace()).getCellSize(i32);
@ -682,7 +697,10 @@ fn sendMouseWheel(
wparam: win32.WPARAM, wparam: win32.WPARAM,
lparam: win32.LPARAM, lparam: win32.LPARAM,
) void { ) void {
const point = win32ext.pointFromLparam(lparam); const frame = tracy.initZone(@src(), .{ .name = "gui sendMouseWheel" });
defer frame.deinit();
var point = win32.pointFromLparam(lparam);
_ = win32.ScreenToClient(hwnd, &point);
const state = stateFromHwnd(hwnd); const state = stateFromHwnd(hwnd);
const dpi = win32.dpiFromHwnd(hwnd); const dpi = win32.dpiFromHwnd(hwnd);
const cell_size = getFont(dpi, getFontSize(), getFontFace()).getCellSize(i32); const cell_size = getFont(dpi, getFontSize(), getFontFace()).getCellSize(i32);
@ -731,10 +749,12 @@ fn sendKey(
wparam: win32.WPARAM, wparam: win32.WPARAM,
lparam: win32.LPARAM, lparam: win32.LPARAM,
) void { ) void {
const frame = tracy.initZone(@src(), .{ .name = "gui sendKey" });
defer frame.deinit();
const state = stateFromHwnd(hwnd); const state = stateFromHwnd(hwnd);
var keyboard_state: [256]u8 = undefined; var keyboard_state: [256]u8 = undefined;
if (0 == win32.GetKeyboardState(&keyboard_state)) fatalWin32( if (0 == win32.GetKeyboardState(&keyboard_state)) win32.panicWin32(
"GetKeyboardState", "GetKeyboardState",
win32.GetLastError(), win32.GetLastError(),
); );
@ -988,6 +1008,8 @@ fn WndProc(
wparam: win32.WPARAM, wparam: win32.WPARAM,
lparam: win32.LPARAM, lparam: win32.LPARAM,
) callconv(std.os.windows.WINAPI) win32.LRESULT { ) callconv(std.os.windows.WINAPI) win32.LRESULT {
const frame = tracy.initZone(@src(), .{ .name = "gui WndProc" });
defer frame.deinit();
var msg_node: windowmsg.MessageNode = undefined; var msg_node: windowmsg.MessageNode = undefined;
msg_node.init(&global_msg_tail, hwnd, msg, wparam, lparam); msg_node.init(&global_msg_tail, hwnd, msg, wparam, lparam);
defer msg_node.deinit(); defer msg_node.deinit();
@ -1035,11 +1057,17 @@ fn WndProc(
return 0; return 0;
}, },
win32.WM_PAINT => { win32.WM_PAINT => {
const frame_ = tracy.initZone(@src(), .{ .name = "gui WM_PAINT" });
defer frame_.deinit();
const state = stateFromHwnd(hwnd); const state = stateFromHwnd(hwnd);
const dpi = win32.dpiFromHwnd(hwnd); const dpi = win32.dpiFromHwnd(hwnd);
const font = getFont(dpi, getFontSize(), getFontFace()); const font = getFont(dpi, getFontSize(), getFontFace());
const client_size = getClientSize(u32, hwnd); const client_size = getClientSize(u32, hwnd);
var ps: win32.PAINTSTRUCT = undefined;
_ = win32.BeginPaint(hwnd, &ps) orelse return win32.panicWin32("BeginPaint", win32.GetLastError());
defer if (0 == win32.EndPaint(hwnd, &ps)) win32.panicWin32("EndPaint", win32.GetLastError());
global.render_cells.resize( global.render_cells.resize(
global.render_cells_arena.allocator(), global.render_cells_arena.allocator(),
global.screen.buf.len, global.screen.buf.len,
@ -1070,6 +1098,8 @@ fn WndProc(
return 0; return 0;
}, },
win32.WM_GETDPISCALEDSIZE => { win32.WM_GETDPISCALEDSIZE => {
const frame_ = tracy.initZone(@src(), .{ .name = "gui WM_GETDPISCALEDSIZE" });
defer frame_.deinit();
const inout_size: *win32.SIZE = @ptrFromInt(@as(usize, @bitCast(lparam))); const inout_size: *win32.SIZE = @ptrFromInt(@as(usize, @bitCast(lparam)));
const new_dpi: u32 = @intCast(0xffffffff & wparam); const new_dpi: u32 = @intCast(0xffffffff & wparam);
// we don't want to update the font with the new dpi until after // we don't want to update the font with the new dpi until after
@ -1105,6 +1135,8 @@ fn WndProc(
return 1; return 1;
}, },
win32.WM_DPICHANGED => { win32.WM_DPICHANGED => {
const frame_ = tracy.initZone(@src(), .{ .name = "gui WM_DPICHANGED" });
defer frame_.deinit();
const state = stateFromHwnd(hwnd); const state = stateFromHwnd(hwnd);
const dpi = win32.dpiFromHwnd(hwnd); const dpi = win32.dpiFromHwnd(hwnd);
if (dpi != win32.hiword(wparam)) @panic("unexpected hiword dpi"); if (dpi != win32.hiword(wparam)) @panic("unexpected hiword dpi");
@ -1115,10 +1147,14 @@ fn WndProc(
return 0; return 0;
}, },
win32.WM_WINDOWPOSCHANGED => { win32.WM_WINDOWPOSCHANGED => {
const frame_ = tracy.initZone(@src(), .{ .name = "gui WM_WINDOWPOSCHANGED" });
defer frame_.deinit();
sendResize(hwnd); sendResize(hwnd);
return 0; return 0;
}, },
win32.WM_SIZING => { win32.WM_SIZING => {
const frame_ = tracy.initZone(@src(), .{ .name = "gui WM_SIZING" });
defer frame_.deinit();
const rect: *win32.RECT = @ptrFromInt(@as(usize, @bitCast(lparam))); const rect: *win32.RECT = @ptrFromInt(@as(usize, @bitCast(lparam)));
const dpi = win32.dpiFromHwnd(hwnd); const dpi = win32.dpiFromHwnd(hwnd);
const font = getFont(dpi, getFontSize(), getFontFace()); const font = getFont(dpi, getFontSize(), getFontFace());
@ -1133,6 +1169,8 @@ fn WndProc(
return 0; return 0;
}, },
win32.WM_DISPLAYCHANGE => { win32.WM_DISPLAYCHANGE => {
const frame_ = tracy.initZone(@src(), .{ .name = "gui WM_DISPLAYCHANGE" });
defer frame_.deinit();
win32.invalidateHwnd(hwnd); win32.invalidateHwnd(hwnd);
return 0; return 0;
}, },
@ -1146,6 +1184,8 @@ fn WndProc(
return WM_APP_EXIT_RESULT; return WM_APP_EXIT_RESULT;
}, },
WM_APP_SET_BACKGROUND => { WM_APP_SET_BACKGROUND => {
const frame_ = tracy.initZone(@src(), .{ .name = "gui WM_APP_SET_BACKGROUND" });
defer frame_.deinit();
const rgb = RGB.from_u24(@intCast(0xffffff & wparam)); const rgb = RGB.from_u24(@intCast(0xffffff & wparam));
render.setBackground( render.setBackground(
&stateFromHwnd(hwnd).render_state, &stateFromHwnd(hwnd).render_state,
@ -1155,6 +1195,8 @@ fn WndProc(
return WM_APP_SET_BACKGROUND_RESULT; return WM_APP_SET_BACKGROUND_RESULT;
}, },
WM_APP_ADJUST_FONTSIZE => { WM_APP_ADJUST_FONTSIZE => {
const frame_ = tracy.initZone(@src(), .{ .name = "gui WM_APP_ADJUST_FONTSIZE" });
defer frame_.deinit();
const state = stateFromHwnd(hwnd); const state = stateFromHwnd(hwnd);
const amount: f32 = @bitCast(@as(u32, @intCast(0xFFFFFFFFF & wparam))); const amount: f32 = @bitCast(@as(u32, @intCast(0xFFFFFFFFF & wparam)));
global.fontsize = @max(getFontSize() + amount, 1.0); global.fontsize = @max(getFontSize() + amount, 1.0);
@ -1163,6 +1205,8 @@ fn WndProc(
return WM_APP_ADJUST_FONTSIZE_RESULT; return WM_APP_ADJUST_FONTSIZE_RESULT;
}, },
WM_APP_SET_FONTSIZE => { WM_APP_SET_FONTSIZE => {
const frame_ = tracy.initZone(@src(), .{ .name = "gui WM_APP_SET_FONTSIZE" });
defer frame_.deinit();
const state = stateFromHwnd(hwnd); const state = stateFromHwnd(hwnd);
const fontsize: f32 = @bitCast(@as(u32, @intCast(0xFFFFFFFFF & wparam))); const fontsize: f32 = @bitCast(@as(u32, @intCast(0xFFFFFFFFF & wparam)));
global.fontsize = @max(fontsize, 1.0); global.fontsize = @max(fontsize, 1.0);
@ -1171,6 +1215,8 @@ fn WndProc(
return WM_APP_SET_FONTSIZE_RESULT; return WM_APP_SET_FONTSIZE_RESULT;
}, },
WM_APP_RESET_FONTSIZE => { WM_APP_RESET_FONTSIZE => {
const frame_ = tracy.initZone(@src(), .{ .name = "gui WM_APP_RESET_FONTSIZE" });
defer frame_.deinit();
const state = stateFromHwnd(hwnd); const state = stateFromHwnd(hwnd);
global.fontsize = null; global.fontsize = null;
updateWindowSize(hwnd, win32.WMSZ_BOTTOMRIGHT, &state.bounds); updateWindowSize(hwnd, win32.WMSZ_BOTTOMRIGHT, &state.bounds);
@ -1178,6 +1224,8 @@ fn WndProc(
return WM_APP_SET_FONTSIZE_RESULT; return WM_APP_SET_FONTSIZE_RESULT;
}, },
WM_APP_SET_FONTFACE => { WM_APP_SET_FONTFACE => {
const frame_ = tracy.initZone(@src(), .{ .name = "gui WM_APP_SET_FONTFACE" });
defer frame_.deinit();
const state = stateFromHwnd(hwnd); const state = stateFromHwnd(hwnd);
setFontFace(@ptrFromInt(wparam)); setFontFace(@ptrFromInt(wparam));
updateWindowSize(hwnd, win32.WMSZ_BOTTOMRIGHT, &state.bounds); updateWindowSize(hwnd, win32.WMSZ_BOTTOMRIGHT, &state.bounds);
@ -1185,6 +1233,8 @@ fn WndProc(
return WM_APP_SET_FONTFACE_RESULT; return WM_APP_SET_FONTFACE_RESULT;
}, },
WM_APP_RESET_FONTFACE => { WM_APP_RESET_FONTFACE => {
const frame_ = tracy.initZone(@src(), .{ .name = "gui WM_APP_RESET_FONTFACE" });
defer frame_.deinit();
const state = stateFromHwnd(hwnd); const state = stateFromHwnd(hwnd);
global.fontface = null; global.fontface = null;
updateWindowSize(hwnd, win32.WMSZ_BOTTOMRIGHT, &state.bounds); updateWindowSize(hwnd, win32.WMSZ_BOTTOMRIGHT, &state.bounds);
@ -1192,11 +1242,15 @@ fn WndProc(
return WM_APP_SET_FONTFACE_RESULT; return WM_APP_SET_FONTFACE_RESULT;
}, },
WM_APP_GET_FONTFACES => { WM_APP_GET_FONTFACES => {
const frame_ = tracy.initZone(@src(), .{ .name = "gui WM_APP_GET_FONTFACES" });
defer frame_.deinit();
const state = stateFromHwnd(hwnd); const state = stateFromHwnd(hwnd);
getFontFaces(state); getFontFaces(state);
return WM_APP_GET_FONTFACES_RESULT; return WM_APP_GET_FONTFACES_RESULT;
}, },
WM_APP_UPDATE_SCREEN => { WM_APP_UPDATE_SCREEN => {
const frame_ = tracy.initZone(@src(), .{ .name = "gui WM_APP_UPDATE_SCREEN" });
defer frame_.deinit();
const screen: *const vaxis.Screen = @ptrFromInt(wparam); const screen: *const vaxis.Screen = @ptrFromInt(wparam);
_ = global.screen_arena.reset(.retain_capacity); _ = global.screen_arena.reset(.retain_capacity);
const buf = global.screen_arena.allocator().alloc(vaxis.Cell, screen.buf.len) catch |e| oom(e); const buf = global.screen_arena.allocator().alloc(vaxis.Cell, screen.buf.len) catch |e| oom(e);
@ -1225,6 +1279,8 @@ fn WndProc(
return WM_APP_UPDATE_SCREEN_RESULT; return WM_APP_UPDATE_SCREEN_RESULT;
}, },
win32.WM_CREATE => { win32.WM_CREATE => {
const frame_ = tracy.initZone(@src(), .{ .name = "gui WM_CREATE" });
defer frame_.deinit();
std.debug.assert(global.state == null); std.debug.assert(global.state == null);
const create_struct: *win32.CREATESTRUCTW = @ptrFromInt(@as(usize, @bitCast(lparam))); const create_struct: *win32.CREATESTRUCTW = @ptrFromInt(@as(usize, @bitCast(lparam)));
const create_args: *CreateWindowArgs = @alignCast(@ptrCast(create_struct.lpCreateParams)); const create_args: *CreateWindowArgs = @alignCast(@ptrCast(create_struct.lpCreateParams));
@ -1242,13 +1298,19 @@ fn WndProc(
// hwnd reference // hwnd reference
@panic("gui window erroneously destroyed"); @panic("gui window erroneously destroyed");
}, },
else => return win32.DefWindowProcW(hwnd, msg, wparam, lparam), else => {
const frame_ = tracy.initZone(@src(), .{ .name = "gui DefWindowProcW" });
defer frame_.deinit();
return win32.DefWindowProcW(hwnd, msg, wparam, lparam);
},
} }
} }
fn sendResize( fn sendResize(
hwnd: win32.HWND, hwnd: win32.HWND,
) void { ) void {
const frame = tracy.initZone(@src(), .{ .name = "gui sendResize" });
defer frame.deinit();
const dpi = win32.dpiFromHwnd(hwnd); const dpi = win32.dpiFromHwnd(hwnd);
const state = stateFromHwnd(hwnd); const state = stateFromHwnd(hwnd);
@ -1281,19 +1343,13 @@ fn renderColorFromVaxis(color: vaxis.Color) render.Color {
}; };
} }
fn fatalWin32(what: []const u8, err: win32.WIN32_ERROR) noreturn {
std.debug.panic("{s} failed with {}", .{ what, err.fmt() });
}
fn fatalHr(what: []const u8, hresult: win32.HRESULT) noreturn {
std.debug.panic("{s} failed, hresult=0x{x}", .{ what, @as(u32, @bitCast(hresult)) });
}
fn deleteObject(obj: ?win32.HGDIOBJ) void { fn deleteObject(obj: ?win32.HGDIOBJ) void {
if (0 == win32.DeleteObject(obj)) fatalWin32("DeleteObject", win32.GetLastError()); if (0 == win32.DeleteObject(obj)) win32.panicWin32("DeleteObject", win32.GetLastError());
} }
fn getClientSize(comptime T: type, hwnd: win32.HWND) XY(T) { fn getClientSize(comptime T: type, hwnd: win32.HWND) XY(T) {
var rect: win32.RECT = undefined; var rect: win32.RECT = undefined;
if (0 == win32.GetClientRect(hwnd, &rect)) if (0 == win32.GetClientRect(hwnd, &rect))
fatalWin32("GetClientRect", win32.GetLastError()); win32.panicWin32("GetClientRect", win32.GetLastError());
std.debug.assert(rect.left == 0); std.debug.assert(rect.left == 0);
std.debug.assert(rect.top == 0); std.debug.assert(rect.top == 0);
return .{ .x = @intCast(rect.right), .y = @intCast(rect.bottom) }; return .{ .x = @intCast(rect.right), .y = @intCast(rect.bottom) };
@ -1305,6 +1361,8 @@ fn calcWindowRect(
maybe_edge: ?win32.WPARAM, maybe_edge: ?win32.WPARAM,
cell_size: XY(i32), cell_size: XY(i32),
) win32.RECT { ) win32.RECT {
const frame = tracy.initZone(@src(), .{ .name = "gui calcWindowRect" });
defer frame.deinit();
const client_inset = getClientInset(dpi); const client_inset = getClientInset(dpi);
const bounding_client_size: XY(i32) = .{ const bounding_client_size: XY(i32) = .{
.x = (bounding_rect.right - bounding_rect.left) - client_inset.x, .x = (bounding_rect.right - bounding_rect.left) - client_inset.x,
@ -1364,7 +1422,7 @@ fn getClientInset(dpi: u32) XY(i32) {
0, 0,
window_style_ex, window_style_ex,
dpi, dpi,
)) fatalWin32( )) win32.panicWin32(
"AdjustWindowRect", "AdjustWindowRect",
win32.GetLastError(), win32.GetLastError(),
); );
@ -1392,5 +1450,5 @@ fn setWindowPosRect(hwnd: win32.HWND, rect: win32.RECT) void {
rect.right - rect.left, rect.right - rect.left,
rect.bottom - rect.top, rect.bottom - rect.top,
.{ .NOZORDER = 1 }, .{ .NOZORDER = 1 },
)) fatalWin32("SetWindowPos", win32.GetLastError()); )) win32.panicWin32("SetWindowPos", win32.GetLastError());
} }

View file

@ -1,17 +1,6 @@
const std = @import("std"); const std = @import("std");
const win32 = @import("win32").everything; const win32 = @import("win32").everything;
// todo: these should be available in zigwin32
fn xFromLparam(lparam: win32.LPARAM) i16 {
return @bitCast(win32.loword(lparam));
}
fn yFromLparam(lparam: win32.LPARAM) i16 {
return @bitCast(win32.hiword(lparam));
}
pub fn pointFromLparam(lparam: win32.LPARAM) win32.POINT {
return win32.POINT{ .x = xFromLparam(lparam), .y = yFromLparam(lparam) };
}
// TODO: update zigwin32 with a way to get the corresponding IID for any COM interface // TODO: update zigwin32 with a way to get the corresponding IID for any COM interface
pub fn queryInterface(obj: anytype, comptime Interface: type) *Interface { pub fn queryInterface(obj: anytype, comptime Interface: type) *Interface {
const obj_basename_start: usize = comptime if (std.mem.lastIndexOfScalar(u8, @typeName(@TypeOf(obj)), '.')) |i| (i + 1) else 0; const obj_basename_start: usize = comptime if (std.mem.lastIndexOfScalar(u8, @typeName(@TypeOf(obj)), '.')) |i| (i + 1) else 0;