Merge branch 'master' into helix-mode-selections
This commit is contained in:
commit
80c8795c3b
17 changed files with 429 additions and 134 deletions
14
build.zig
14
build.zig
|
@ -1,6 +1,8 @@
|
|||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const optimize_deps = .ReleaseFast;
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
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;
|
||||
|
@ -227,14 +229,14 @@ pub fn build_exe(
|
|||
|
||||
const syntax_dep = b.dependency("syntax", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.optimize = optimize_deps,
|
||||
.use_tree_sitter = use_tree_sitter,
|
||||
});
|
||||
const syntax_mod = syntax_dep.module("syntax");
|
||||
|
||||
const thespian_dep = b.dependency("thespian", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.optimize = optimize_deps,
|
||||
.enable_tracy = tracy_enabled,
|
||||
});
|
||||
|
||||
|
@ -316,16 +318,13 @@ pub fn build_exe(
|
|||
const renderer_mod = blk: {
|
||||
if (gui) switch (target.result.os.tag) {
|
||||
.windows => {
|
||||
const direct2d_dep = b.lazyDependency("direct2d", .{}) orelse break :blk tui_renderer_mod;
|
||||
|
||||
const win32_dep = direct2d_dep.builder.dependency("win32", .{});
|
||||
const win32_mod = win32_dep.module("zigwin32");
|
||||
const win32_dep = b.lazyDependency("win32", .{}) orelse break :blk tui_renderer_mod;
|
||||
const win32_mod = win32_dep.module("win32");
|
||||
const gui_mod = b.createModule(.{
|
||||
.root_source_file = b.path("src/win32/gui.zig"),
|
||||
.imports = &.{
|
||||
.{ .name = "build_options", .module = options_mod },
|
||||
.{ .name = "win32", .module = win32_mod },
|
||||
.{ .name = "ddui", .module = direct2d_dep.module("ddui") },
|
||||
.{ .name = "cbor", .module = cbor_mod },
|
||||
.{ .name = "thespian", .module = thespian_mod },
|
||||
.{ .name = "input", .module = input_mod },
|
||||
|
@ -333,6 +332,7 @@ pub fn build_exe(
|
|||
.{ .name = "vaxis", .module = vaxis_mod },
|
||||
.{ .name = "color", .module = color_mod },
|
||||
.{ .name = "gui_config", .module = gui_config_mod },
|
||||
.{ .name = "tracy", .module = tracy_mod },
|
||||
},
|
||||
});
|
||||
gui_mod.addIncludePath(b.path("src/win32"));
|
||||
|
|
|
@ -10,16 +10,16 @@
|
|||
.hash = "1220930a42f8da3fb7f723e3ad3f6dcc6db76327dd8d26274566423192d53e91b2bb",
|
||||
},
|
||||
.tracy = .{
|
||||
.url = "https://github.com/neurocyte/zig-tracy/archive/58999b786089e5319dd0707f6afbfca04c6340e7.tar.gz",
|
||||
.hash = "1220a2c8f8db1b5265458ac967ea1f7cc0a8ddcd1d774df3b73d86c4f529aadbfb94",
|
||||
.url = "https://github.com/neurocyte/zig-tracy/archive/e04e31c64498149a324491b8534758e6af43a5c2.tar.gz",
|
||||
.hash = "1220d0fb2bff7b453dbb39d1db3eb472b6680e2564f2b23b0e947671be47bbdd188f",
|
||||
},
|
||||
.dizzy = .{
|
||||
.url = "https://github.com/neurocyte/dizzy/archive/455d18369cbb2a0458ba70be919cd378338d695e.tar.gz",
|
||||
.hash = "1220220dbc7fe91c1c54438193ca765cebbcb7d58f35cdcaee404a9d2245a42a4362",
|
||||
},
|
||||
.thespian = .{
|
||||
.url = "https://github.com/neurocyte/thespian/archive/b7ace533875217f2b002f0ac8dfe32169cab7138.tar.gz",
|
||||
.hash = "1220d25045c26cc8d2d46995d670ce10b64d0e3a3fa23cfded9a35d353b4d08cff08",
|
||||
.url = "https://github.com/neurocyte/thespian/archive/d2b65df7c59b58f5e86b1ce3403cd3589d06a367.tar.gz",
|
||||
.hash = "122040a2dcb9569ee26ae9c205f944db4ee9e3eb7c9d5ec610c7d84cf82ab6909fa9",
|
||||
},
|
||||
.themes = .{
|
||||
.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",
|
||||
.hash = "1220755ea2a5aa6bb3713437aaafefd44812169fe43f1da755c3ee6101b85940f441",
|
||||
},
|
||||
.direct2d = .{
|
||||
.url = "https://github.com/marler8997/direct2d-zig/archive/0d031389a26653bb71f81c2340d1b8ba6bd339c3.tar.gz",
|
||||
.hash = "122069b40656962c6ba9b9b3f9f882ba2e9cf4c5e1afebac7b7501404129e6bb4705",
|
||||
.win32 = .{
|
||||
.url = "https://github.com/marlersoft/zigwin32/archive/259b6f353a48968d7e3171573db4fd898b046188.tar.gz",
|
||||
.hash = "1220925614447b54ccc9894bbba8b202c6a8b750267890edab7732064867e46f3217",
|
||||
.lazy = true,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
DESTDIR="$(pwd)/release"
|
||||
BASEDIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
APPNAME="$(basename "$BASEDIR")"
|
||||
|
||||
cd "$BASEDIR"
|
||||
|
||||
if [ -e "release" ]; then
|
||||
if [ -e "$DESTDIR" ]; then
|
||||
echo directory \"release\" already exists
|
||||
exit 1
|
||||
fi
|
||||
|
@ -17,9 +18,9 @@ echo running tests...
|
|||
|
||||
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)
|
||||
TARGETS=$(/bin/ls)
|
||||
|
@ -44,7 +45,7 @@ for tarfile in $TARFILES; do
|
|||
sha256sum -b "$tarfile" > "${tarfile}.sha256"
|
||||
done
|
||||
|
||||
echo "done making release $VERSION"
|
||||
echo "done making release $VERSION @ $DESTDIR"
|
||||
echo
|
||||
|
||||
/bin/ls -lah
|
||||
|
|
|
@ -10,6 +10,7 @@ const max_imbalance = 7;
|
|||
pub const Root = *const Node;
|
||||
pub const unicode = @import("unicode.zig");
|
||||
|
||||
pub const Manager = @import("Manager.zig");
|
||||
pub const Cursor = @import("Cursor.zig");
|
||||
pub const View = @import("View.zig");
|
||||
pub const Selection = @import("Selection.zig");
|
||||
|
@ -43,6 +44,9 @@ undo_history: ?*UndoNode = null,
|
|||
redo_history: ?*UndoNode = null,
|
||||
curr_history: ?*UndoNode = null,
|
||||
|
||||
mtime: i64,
|
||||
utime: i64,
|
||||
|
||||
pub const EolMode = enum { lf, crlf };
|
||||
pub const EolModeTag = @typeInfo(EolMode).Enum.tag_type;
|
||||
|
||||
|
@ -143,7 +147,7 @@ pub const Leaf = struct {
|
|||
bol: 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)
|
||||
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);
|
||||
|
@ -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 arena_a = if (builtin.is_test) allocator else std.heap.page_allocator;
|
||||
self.* = .{
|
||||
|
@ -1051,6 +1055,8 @@ pub fn create(allocator: Allocator) !*Self {
|
|||
.allocator = self.arena.allocator(),
|
||||
.external_allocator = allocator,
|
||||
.root = try Node.new(self.allocator, &empty_leaf, &empty_leaf),
|
||||
.mtime = std.time.milliTimestamp(),
|
||||
.utime = std.time.milliTimestamp(),
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
@ -1062,12 +1068,27 @@ pub fn deinit(self: *Self) void {
|
|||
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;
|
||||
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 cr = '\r';
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
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.file_path = try self.allocator.dupe(u8, file_path);
|
||||
self.last_save = self.root;
|
||||
self.last_save_eol_mode = self.file_eol_mode;
|
||||
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) {
|
||||
error.FileNotFound => return self.new_file(file_exists),
|
||||
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);
|
||||
}
|
||||
|
||||
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 eol_mode: EolMode = .lf;
|
||||
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_utf8_sanitized = utf8_sanitized;
|
||||
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 {
|
||||
|
@ -1161,7 +1240,7 @@ pub fn store_to_string(self: *const Self, allocator: Allocator, eol_mode: EolMod
|
|||
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 BufferedWriter = std.io.BufferedWriter(buffer_size, std.fs.File.Writer);
|
||||
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();
|
||||
}
|
||||
|
||||
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);
|
||||
var atomic = try cwd().atomicFile(file_path, .{ .mode = stat.mode });
|
||||
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();
|
||||
}
|
||||
|
||||
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 });
|
||||
defer file.close();
|
||||
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) {
|
||||
error.FileNotFound => try self.store_to_new_file_const(file_path),
|
||||
else => return e,
|
||||
|
@ -1213,6 +1331,7 @@ pub fn version(self: *const Self) usize {
|
|||
|
||||
pub fn update(self: *Self, root: Root) void {
|
||||
self.root = root;
|
||||
self.mtime = std.time.milliTimestamp();
|
||||
}
|
||||
|
||||
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.root = h.root;
|
||||
self.push_redo(r);
|
||||
self.mtime = std.time.milliTimestamp();
|
||||
return h.meta;
|
||||
}
|
||||
|
||||
|
@ -1274,5 +1394,6 @@ pub fn redo(self: *Self) error{Stop}![]const u8 {
|
|||
self.curr_history = h;
|
||||
self.root = h.root;
|
||||
self.push_undo(u);
|
||||
self.mtime = std.time.milliTimestamp();
|
||||
return h.meta;
|
||||
}
|
||||
|
|
87
src/buffer/Manager.zig
Normal file
87
src/buffer/Manager.zig
Normal 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);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"project": {
|
||||
"press": [
|
||||
["ctrl+shift+e", "switch_buffers"],
|
||||
["ctrl+0", "reset_fontsize"],
|
||||
["ctrl+plus", "adjust_fontsize", 1.0],
|
||||
["ctrl+minus", "adjust_fontsize", -1.0],
|
||||
|
@ -250,6 +251,7 @@
|
|||
["ctrl+p", "palette_menu_up"],
|
||||
["ctrl+n", "palette_menu_down"],
|
||||
["ctrl+e", "palette_menu_down"],
|
||||
["ctrl+shift+e", "palette_menu_down"],
|
||||
["ctrl+r", "palette_menu_down"],
|
||||
["ctrl+t", "palette_menu_down"],
|
||||
["ctrl+v", "system_paste"],
|
||||
|
|
|
@ -19,6 +19,8 @@ const gui = @import("gui");
|
|||
const DropWriter = gui.DropWriter;
|
||||
pub const style = StyleBits;
|
||||
|
||||
pub const panic = win32.messageBoxThenPanic(.{ .title = "Flow Panic" });
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
vx: vaxis.Vaxis,
|
||||
|
||||
|
@ -84,25 +86,6 @@ pub fn deinit(self: *Self) void {
|
|||
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 {
|
||||
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");
|
||||
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);
|
||||
} else {
|
||||
self.allocator.free(title);
|
||||
|
|
|
@ -242,6 +242,7 @@ pub const Editor = struct {
|
|||
|
||||
file_path: ?[]const u8,
|
||||
buffer: ?*Buffer,
|
||||
buffer_manager: *Buffer.Manager,
|
||||
lsp_version: usize = 1,
|
||||
pause_undo: bool = false,
|
||||
|
||||
|
@ -382,7 +383,7 @@ pub const Editor = struct {
|
|||
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 frame_rate = tp.env.get().num("frame-rate");
|
||||
const indent_size = tui.current().config.indent_size;
|
||||
|
@ -396,6 +397,7 @@ pub const Editor = struct {
|
|||
.logger = logger,
|
||||
.file_path = null,
|
||||
.buffer = null,
|
||||
.buffer_manager = buffer_manager,
|
||||
.handlers = EventHandler.List.init(allocator),
|
||||
.animation_lag = get_animation_max_lag(),
|
||||
.animation_frame_rate = frame_rate,
|
||||
|
@ -417,7 +419,7 @@ pub const Editor = struct {
|
|||
self.matches.deinit();
|
||||
self.handlers.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();
|
||||
}
|
||||
|
||||
|
@ -483,28 +485,16 @@ pub const Editor = struct {
|
|||
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 {
|
||||
var new_buf = try Buffer.create(self.allocator);
|
||||
errdefer new_buf.deinit();
|
||||
try new_buf.load_from_file_and_update(file_path);
|
||||
return self.open_buffer(file_path, new_buf);
|
||||
return self.open_buffer(file_path, try self.buffer_manager.open_file(file_path));
|
||||
}
|
||||
|
||||
fn open_scratch(self: *Self, file_path: []const u8, content: []const u8) !void {
|
||||
var new_buf = try Buffer.create(self.allocator);
|
||||
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);
|
||||
return self.open_buffer(file_path, try self.buffer_manager.open_scratch(file_path, content));
|
||||
}
|
||||
|
||||
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.get_primary().reset();
|
||||
self.file_path = try self.allocator.dupe(u8, file_path);
|
||||
|
@ -543,17 +533,7 @@ pub const Editor = struct {
|
|||
}
|
||||
|
||||
fn close(self: *Self) !void {
|
||||
return self.close_internal(false);
|
||||
}
|
||||
|
||||
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();
|
||||
if (self.buffer) |b_mut| self.buffer_manager.retire(b_mut);
|
||||
self.buffer = null;
|
||||
self.plane.erase();
|
||||
self.plane.home();
|
||||
|
@ -3805,7 +3785,8 @@ pub const Editor = struct {
|
|||
|
||||
pub fn close_file_without_saving(self: *Self, _: Context) Result {
|
||||
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" };
|
||||
|
||||
|
@ -4794,8 +4775,8 @@ pub const Editor = struct {
|
|||
pub const set_file_type_meta = .{ .arguments = &.{.string} };
|
||||
};
|
||||
|
||||
pub fn create(allocator: Allocator, parent: Widget) !Widget {
|
||||
return EditorWidget.create(allocator, parent);
|
||||
pub fn create(allocator: Allocator, parent: Widget, buffer_manager: *Buffer.Manager) !Widget {
|
||||
return EditorWidget.create(allocator, parent, buffer_manager);
|
||||
}
|
||||
|
||||
pub const EditorWidget = struct {
|
||||
|
@ -4817,10 +4798,10 @@ pub const EditorWidget = struct {
|
|||
const Self = @This();
|
||||
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 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);
|
||||
const editorWidget = Widget.to(self);
|
||||
try container.add(try editor_gutter.create(allocator, container.widget(), editorWidget, &self.editor));
|
||||
|
@ -4829,7 +4810,7 @@ pub const EditorWidget = struct {
|
|||
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.*);
|
||||
errdefer n.deinit();
|
||||
|
||||
|
@ -4838,7 +4819,7 @@ pub const EditorWidget = struct {
|
|||
.plane = n,
|
||||
.editor = undefined,
|
||||
};
|
||||
self.editor.init(allocator, n);
|
||||
self.editor.init(allocator, n, buffer_manager);
|
||||
errdefer self.editor.deinit();
|
||||
try self.editor.push_cursor();
|
||||
}
|
||||
|
|
|
@ -245,6 +245,12 @@ const cmds = struct {
|
|||
const Ctx = command.Context;
|
||||
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 {
|
||||
self.menu.select_down();
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ const build_options = @import("build_options");
|
|||
const Plane = @import("renderer").Plane;
|
||||
const input = @import("input");
|
||||
const command = @import("command");
|
||||
const BufferManager = @import("Buffer").Manager;
|
||||
|
||||
const tui = @import("tui.zig");
|
||||
const Box = @import("Box.zig");
|
||||
|
@ -47,6 +48,7 @@ active_view: ?usize = 0,
|
|||
panels: ?*WidgetList = null,
|
||||
last_match_text: ?[]const u8 = null,
|
||||
location_history: location_history,
|
||||
buffer_manager: BufferManager,
|
||||
file_stack: std.ArrayList([]const u8),
|
||||
find_in_files_state: enum { init, adding, done } = .done,
|
||||
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),
|
||||
.views = undefined,
|
||||
.views_widget = undefined,
|
||||
.buffer_manager = BufferManager.init(allocator),
|
||||
};
|
||||
try self.commands.init(self);
|
||||
const w = Widget.to(self);
|
||||
|
@ -104,6 +107,7 @@ pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
|
|||
self.commands.deinit();
|
||||
self.widgets.deinit(allocator);
|
||||
self.floating_views.deinit();
|
||||
self.buffer_manager.deinit();
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
|
@ -237,14 +241,7 @@ fn toggle_view(self: *Self, view: anytype) !void {
|
|||
}
|
||||
|
||||
fn check_all_not_dirty(self: *const Self) command.Result {
|
||||
for (self.editors.items) |editor|
|
||||
if (editor.is_dirty())
|
||||
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())
|
||||
if (self.buffer_manager.is_dirty())
|
||||
return tp.exit("unsaved changes");
|
||||
}
|
||||
|
||||
|
@ -296,6 +293,8 @@ const cmds = struct {
|
|||
self.clear_find_in_files_results(.diagnostics);
|
||||
if (self.file_list_type == .diagnostics and self.is_panel_view_showing(filelist_view))
|
||||
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);
|
||||
const project = tp.env.get().str("project");
|
||||
tui.current().rdr.set_terminal_working_directory(project);
|
||||
|
@ -352,7 +351,6 @@ const cmds = struct {
|
|||
|
||||
if (!same_file) {
|
||||
if (self.get_active_editor()) |editor| {
|
||||
try self.check_active_not_dirty();
|
||||
editor.send_editor_jump_source() catch {};
|
||||
}
|
||||
try self.create_editor();
|
||||
|
@ -395,7 +393,6 @@ const cmds = struct {
|
|||
pub const open_gui_config_meta = .{ .description = "Edit gui configuration file" };
|
||||
|
||||
pub fn create_scratch_buffer(self: *Self, ctx: Ctx) Result {
|
||||
try self.check_all_not_dirty();
|
||||
tui.reset_drag_context();
|
||||
try self.create_editor();
|
||||
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 {};
|
||||
try self.delete_active_view();
|
||||
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);
|
||||
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");
|
||||
|
|
|
@ -11,7 +11,6 @@ pub const create = Type.create;
|
|||
|
||||
pub fn load_entries(self: *Type) error{ Exit, OutOfMemory }!void {
|
||||
const editor = tui.get_active_editor() orelse return;
|
||||
if (editor.is_dirty()) return tp.exit("unsaved changes");
|
||||
if (editor.file_path) |old_path|
|
||||
if (std.mem.lastIndexOf(u8, old_path, "/")) |pos|
|
||||
try self.file_path.appendSlice(old_path[0 .. pos + 1]);
|
||||
|
|
49
src/tui/mode/overlay/buffer_palette.zig
Normal file
49
src/tui/mode/overlay/buffer_palette.zig
Normal 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);
|
||||
}
|
|
@ -10,6 +10,7 @@ const keybind = @import("keybind");
|
|||
const project_manager = @import("project_manager");
|
||||
const command = @import("command");
|
||||
const EventHandler = @import("EventHandler");
|
||||
const BufferManager = @import("Buffer").Manager;
|
||||
|
||||
const tui = @import("../../tui.zig");
|
||||
const MessageFilter = @import("../../MessageFilter.zig");
|
||||
|
@ -34,6 +35,7 @@ need_reset: bool = false,
|
|||
need_select_first: bool = true,
|
||||
longest: usize = 0,
|
||||
commands: Commands = undefined,
|
||||
buffer_manager: ?*BufferManager,
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator) !tui.Mode {
|
||||
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,
|
||||
.label = "Search files by name",
|
||||
}))).dynamic_cast(InputBox.State(*Self)) orelse unreachable,
|
||||
.buffer_manager = tui.get_buffer_manager(),
|
||||
};
|
||||
try self.commands.init(self);
|
||||
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));
|
||||
}
|
||||
|
||||
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_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;
|
||||
|
@ -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))
|
||||
file_path = "#ERROR#";
|
||||
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 {};
|
||||
var buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||
var removed_prefix: usize = 0;
|
||||
|
|
|
@ -56,6 +56,7 @@ mouse_idle_timer: ?tp.Cancellable = null,
|
|||
default_cursor: keybind.CursorShape = .default,
|
||||
fontface: []const u8 = "",
|
||||
fontfaces: ?std.ArrayList([]const u8) = null,
|
||||
enable_mouse_idle_timer: bool = false,
|
||||
|
||||
const keepalive = std.time.us_per_day * 365; // one year
|
||||
const idle_frames = 0;
|
||||
|
@ -222,6 +223,7 @@ fn listen_sigwinch(self: *Self) tp.result {
|
|||
}
|
||||
|
||||
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);
|
||||
if (self.mouse_idle_timer) |*t| {
|
||||
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"})) {
|
||||
self.resize();
|
||||
const box = self.screen();
|
||||
message("{d}x{d}", .{ box.w, box.h });
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -811,6 +815,11 @@ const cmds = struct {
|
|||
}
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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 {};
|
||||
}
|
||||
|
|
|
@ -309,7 +309,7 @@ pub fn paint(
|
|||
for (0..shader_row_count) |row| {
|
||||
const src_row = blk: {
|
||||
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 dst_row_offset = row * shader_col_count;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const std = @import("std");
|
||||
const tracy = @import("tracy");
|
||||
const build_options = @import("build_options");
|
||||
const root = @import("root");
|
||||
|
||||
|
@ -92,6 +93,8 @@ const window_style_ex = win32.WINDOW_EX_STYLE{
|
|||
const window_style = win32.WS_OVERLAPPEDWINDOW;
|
||||
|
||||
pub fn init() void {
|
||||
const frame = tracy.initZone(@src(), .{ .name = "gui init" });
|
||||
defer frame.deinit();
|
||||
std.debug.assert(!global.init_called);
|
||||
global.init_called = true;
|
||||
render.init(.{});
|
||||
|
@ -102,6 +105,8 @@ const Icons = struct {
|
|||
large: win32.HICON,
|
||||
};
|
||||
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_y = win32.GetSystemMetricsForDpi(@intFromEnum(win32.SM_CYSMICON), dpi.y);
|
||||
const large_x = win32.GetSystemMetricsForDpi(@intFromEnum(win32.SM_CXICON), dpi.x);
|
||||
|
@ -118,7 +123,7 @@ fn getIcons(dpi: XY(u32)) Icons {
|
|||
small_x,
|
||||
small_y,
|
||||
win32.LR_SHARED,
|
||||
) orelse fatalWin32("LoadImage for small icon", win32.GetLastError());
|
||||
) orelse win32.panicWin32("LoadImage for small icon", win32.GetLastError());
|
||||
const large = win32.LoadImageW(
|
||||
win32.GetModuleHandleW(null),
|
||||
@ptrFromInt(c.ID_ICON_FLOW),
|
||||
|
@ -126,7 +131,7 @@ fn getIcons(dpi: XY(u32)) Icons {
|
|||
large_x,
|
||||
large_y,
|
||||
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) };
|
||||
}
|
||||
|
||||
|
@ -190,6 +195,8 @@ fn getFontSize() f32 {
|
|||
}
|
||||
|
||||
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 (font.dpi == dpi and font.size == size and font.face.eql(face))
|
||||
return font.render_object;
|
||||
|
@ -251,6 +258,8 @@ fn calcWindowPlacement(
|
|||
initial_window_x: u16,
|
||||
initial_window_y: u16,
|
||||
) WindowPlacement {
|
||||
const frame = tracy.initZone(@src(), .{ .name = "gui calcWindowPlacement" });
|
||||
defer frame.deinit();
|
||||
var result = WindowPlacement.default;
|
||||
|
||||
const monitor = maybe_monitor orelse return result;
|
||||
|
@ -259,7 +268,7 @@ fn calcWindowPlacement(
|
|||
var info: win32.MONITORINFO = undefined;
|
||||
info.cbSize = @sizeOf(win32.MONITORINFO);
|
||||
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;
|
||||
}
|
||||
break :blk info.rcWork;
|
||||
|
@ -326,7 +335,7 @@ fn entry(pid: thespian.pid) !void {
|
|||
},
|
||||
win32.MONITOR_DEFAULTTOPRIMARY,
|
||||
) orelse {
|
||||
std.log.warn("MonitorFromPoint failed with {}", .{win32.GetLastError().fmt()});
|
||||
std.log.warn("MonitorFromPoint failed, error={}", .{win32.GetLastError()});
|
||||
break :blk null;
|
||||
};
|
||||
};
|
||||
|
@ -375,7 +384,7 @@ fn entry(pid: thespian.pid) !void {
|
|||
.lpszClassName = CLASS_NAME,
|
||||
.hIconSm = global.icons.small,
|
||||
};
|
||||
if (0 == win32.RegisterClassExW(&wc)) fatalWin32(
|
||||
if (0 == win32.RegisterClassExW(&wc)) win32.panicWin32(
|
||||
"RegisterClass for main window",
|
||||
win32.GetLastError(),
|
||||
);
|
||||
|
@ -394,7 +403,7 @@ fn entry(pid: thespian.pid) !void {
|
|||
null, // Menu
|
||||
win32.GetModuleHandleW(null),
|
||||
@ptrCast(&create_args),
|
||||
) orelse fatalWin32("CreateWindow", win32.GetLastError());
|
||||
) orelse win32.panicWin32("CreateWindow", win32.GetLastError());
|
||||
// NEVER DESTROY THE WINDOW!
|
||||
// This allows us to send the hwnd to other thread/parts
|
||||
// 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);
|
||||
|
||||
// try some things to bring our window to the top
|
||||
|
@ -531,12 +540,14 @@ fn updateWindowSize(
|
|||
edge: ?win32.WPARAM,
|
||||
bounds_ref: *?WindowBounds,
|
||||
) void {
|
||||
const frame = tracy.initZone(@src(), .{ .name = "gui updateWindowSize" });
|
||||
defer frame.deinit();
|
||||
const dpi = win32.dpiFromHwnd(hwnd);
|
||||
const font = getFont(dpi, getFontSize(), getFontFace());
|
||||
const cell_size = font.getCellSize(i32);
|
||||
|
||||
var window_rect: win32.RECT = undefined;
|
||||
if (0 == win32.GetWindowRect(hwnd, &window_rect)) fatalWin32(
|
||||
if (0 == win32.GetWindowRect(hwnd, &window_rect)) win32.panicWin32(
|
||||
"GetWindowRect",
|
||||
win32.GetLastError(),
|
||||
);
|
||||
|
@ -564,6 +575,8 @@ fn updateWindowSize(
|
|||
}
|
||||
|
||||
fn getFontFaces(state: *State) void {
|
||||
const frame = tracy.initZone(@src(), .{ .name = "gui getFontFaces" });
|
||||
defer frame.deinit();
|
||||
const fonts = render.Fonts.init();
|
||||
defer fonts.deinit();
|
||||
var buf: [FontFace.max * 2]u8 = undefined;
|
||||
|
@ -631,7 +644,9 @@ fn sendMouse(
|
|||
wparam: win32.WPARAM,
|
||||
lparam: win32.LPARAM,
|
||||
) 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 dpi = win32.dpiFromHwnd(hwnd);
|
||||
const cell_size = getFont(dpi, getFontSize(), getFontFace()).getCellSize(i32);
|
||||
|
@ -682,7 +697,10 @@ fn sendMouseWheel(
|
|||
wparam: win32.WPARAM,
|
||||
lparam: win32.LPARAM,
|
||||
) 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 dpi = win32.dpiFromHwnd(hwnd);
|
||||
const cell_size = getFont(dpi, getFontSize(), getFontFace()).getCellSize(i32);
|
||||
|
@ -731,10 +749,12 @@ fn sendKey(
|
|||
wparam: win32.WPARAM,
|
||||
lparam: win32.LPARAM,
|
||||
) void {
|
||||
const frame = tracy.initZone(@src(), .{ .name = "gui sendKey" });
|
||||
defer frame.deinit();
|
||||
const state = stateFromHwnd(hwnd);
|
||||
|
||||
var keyboard_state: [256]u8 = undefined;
|
||||
if (0 == win32.GetKeyboardState(&keyboard_state)) fatalWin32(
|
||||
if (0 == win32.GetKeyboardState(&keyboard_state)) win32.panicWin32(
|
||||
"GetKeyboardState",
|
||||
win32.GetLastError(),
|
||||
);
|
||||
|
@ -988,6 +1008,8 @@ fn WndProc(
|
|||
wparam: win32.WPARAM,
|
||||
lparam: win32.LPARAM,
|
||||
) callconv(std.os.windows.WINAPI) win32.LRESULT {
|
||||
const frame = tracy.initZone(@src(), .{ .name = "gui WndProc" });
|
||||
defer frame.deinit();
|
||||
var msg_node: windowmsg.MessageNode = undefined;
|
||||
msg_node.init(&global_msg_tail, hwnd, msg, wparam, lparam);
|
||||
defer msg_node.deinit();
|
||||
|
@ -1035,11 +1057,17 @@ fn WndProc(
|
|||
return 0;
|
||||
},
|
||||
win32.WM_PAINT => {
|
||||
const frame_ = tracy.initZone(@src(), .{ .name = "gui WM_PAINT" });
|
||||
defer frame_.deinit();
|
||||
const state = stateFromHwnd(hwnd);
|
||||
const dpi = win32.dpiFromHwnd(hwnd);
|
||||
const font = getFont(dpi, getFontSize(), getFontFace());
|
||||
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_arena.allocator(),
|
||||
global.screen.buf.len,
|
||||
|
@ -1070,6 +1098,8 @@ fn WndProc(
|
|||
return 0;
|
||||
},
|
||||
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 new_dpi: u32 = @intCast(0xffffffff & wparam);
|
||||
// we don't want to update the font with the new dpi until after
|
||||
|
@ -1105,6 +1135,8 @@ fn WndProc(
|
|||
return 1;
|
||||
},
|
||||
win32.WM_DPICHANGED => {
|
||||
const frame_ = tracy.initZone(@src(), .{ .name = "gui WM_DPICHANGED" });
|
||||
defer frame_.deinit();
|
||||
const state = stateFromHwnd(hwnd);
|
||||
const dpi = win32.dpiFromHwnd(hwnd);
|
||||
if (dpi != win32.hiword(wparam)) @panic("unexpected hiword dpi");
|
||||
|
@ -1115,10 +1147,14 @@ fn WndProc(
|
|||
return 0;
|
||||
},
|
||||
win32.WM_WINDOWPOSCHANGED => {
|
||||
const frame_ = tracy.initZone(@src(), .{ .name = "gui WM_WINDOWPOSCHANGED" });
|
||||
defer frame_.deinit();
|
||||
sendResize(hwnd);
|
||||
return 0;
|
||||
},
|
||||
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 dpi = win32.dpiFromHwnd(hwnd);
|
||||
const font = getFont(dpi, getFontSize(), getFontFace());
|
||||
|
@ -1133,6 +1169,8 @@ fn WndProc(
|
|||
return 0;
|
||||
},
|
||||
win32.WM_DISPLAYCHANGE => {
|
||||
const frame_ = tracy.initZone(@src(), .{ .name = "gui WM_DISPLAYCHANGE" });
|
||||
defer frame_.deinit();
|
||||
win32.invalidateHwnd(hwnd);
|
||||
return 0;
|
||||
},
|
||||
|
@ -1146,6 +1184,8 @@ fn WndProc(
|
|||
return WM_APP_EXIT_RESULT;
|
||||
},
|
||||
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));
|
||||
render.setBackground(
|
||||
&stateFromHwnd(hwnd).render_state,
|
||||
|
@ -1155,6 +1195,8 @@ fn WndProc(
|
|||
return WM_APP_SET_BACKGROUND_RESULT;
|
||||
},
|
||||
WM_APP_ADJUST_FONTSIZE => {
|
||||
const frame_ = tracy.initZone(@src(), .{ .name = "gui WM_APP_ADJUST_FONTSIZE" });
|
||||
defer frame_.deinit();
|
||||
const state = stateFromHwnd(hwnd);
|
||||
const amount: f32 = @bitCast(@as(u32, @intCast(0xFFFFFFFFF & wparam)));
|
||||
global.fontsize = @max(getFontSize() + amount, 1.0);
|
||||
|
@ -1163,6 +1205,8 @@ fn WndProc(
|
|||
return WM_APP_ADJUST_FONTSIZE_RESULT;
|
||||
},
|
||||
WM_APP_SET_FONTSIZE => {
|
||||
const frame_ = tracy.initZone(@src(), .{ .name = "gui WM_APP_SET_FONTSIZE" });
|
||||
defer frame_.deinit();
|
||||
const state = stateFromHwnd(hwnd);
|
||||
const fontsize: f32 = @bitCast(@as(u32, @intCast(0xFFFFFFFFF & wparam)));
|
||||
global.fontsize = @max(fontsize, 1.0);
|
||||
|
@ -1171,6 +1215,8 @@ fn WndProc(
|
|||
return WM_APP_SET_FONTSIZE_RESULT;
|
||||
},
|
||||
WM_APP_RESET_FONTSIZE => {
|
||||
const frame_ = tracy.initZone(@src(), .{ .name = "gui WM_APP_RESET_FONTSIZE" });
|
||||
defer frame_.deinit();
|
||||
const state = stateFromHwnd(hwnd);
|
||||
global.fontsize = null;
|
||||
updateWindowSize(hwnd, win32.WMSZ_BOTTOMRIGHT, &state.bounds);
|
||||
|
@ -1178,6 +1224,8 @@ fn WndProc(
|
|||
return WM_APP_SET_FONTSIZE_RESULT;
|
||||
},
|
||||
WM_APP_SET_FONTFACE => {
|
||||
const frame_ = tracy.initZone(@src(), .{ .name = "gui WM_APP_SET_FONTFACE" });
|
||||
defer frame_.deinit();
|
||||
const state = stateFromHwnd(hwnd);
|
||||
setFontFace(@ptrFromInt(wparam));
|
||||
updateWindowSize(hwnd, win32.WMSZ_BOTTOMRIGHT, &state.bounds);
|
||||
|
@ -1185,6 +1233,8 @@ fn WndProc(
|
|||
return WM_APP_SET_FONTFACE_RESULT;
|
||||
},
|
||||
WM_APP_RESET_FONTFACE => {
|
||||
const frame_ = tracy.initZone(@src(), .{ .name = "gui WM_APP_RESET_FONTFACE" });
|
||||
defer frame_.deinit();
|
||||
const state = stateFromHwnd(hwnd);
|
||||
global.fontface = null;
|
||||
updateWindowSize(hwnd, win32.WMSZ_BOTTOMRIGHT, &state.bounds);
|
||||
|
@ -1192,11 +1242,15 @@ fn WndProc(
|
|||
return WM_APP_SET_FONTFACE_RESULT;
|
||||
},
|
||||
WM_APP_GET_FONTFACES => {
|
||||
const frame_ = tracy.initZone(@src(), .{ .name = "gui WM_APP_GET_FONTFACES" });
|
||||
defer frame_.deinit();
|
||||
const state = stateFromHwnd(hwnd);
|
||||
getFontFaces(state);
|
||||
return WM_APP_GET_FONTFACES_RESULT;
|
||||
},
|
||||
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);
|
||||
_ = global.screen_arena.reset(.retain_capacity);
|
||||
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;
|
||||
},
|
||||
win32.WM_CREATE => {
|
||||
const frame_ = tracy.initZone(@src(), .{ .name = "gui WM_CREATE" });
|
||||
defer frame_.deinit();
|
||||
std.debug.assert(global.state == null);
|
||||
const create_struct: *win32.CREATESTRUCTW = @ptrFromInt(@as(usize, @bitCast(lparam)));
|
||||
const create_args: *CreateWindowArgs = @alignCast(@ptrCast(create_struct.lpCreateParams));
|
||||
|
@ -1242,13 +1298,19 @@ fn WndProc(
|
|||
// hwnd reference
|
||||
@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(
|
||||
hwnd: win32.HWND,
|
||||
) void {
|
||||
const frame = tracy.initZone(@src(), .{ .name = "gui sendResize" });
|
||||
defer frame.deinit();
|
||||
const dpi = win32.dpiFromHwnd(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 {
|
||||
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) {
|
||||
var rect: win32.RECT = undefined;
|
||||
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.top == 0);
|
||||
return .{ .x = @intCast(rect.right), .y = @intCast(rect.bottom) };
|
||||
|
@ -1305,6 +1361,8 @@ fn calcWindowRect(
|
|||
maybe_edge: ?win32.WPARAM,
|
||||
cell_size: XY(i32),
|
||||
) win32.RECT {
|
||||
const frame = tracy.initZone(@src(), .{ .name = "gui calcWindowRect" });
|
||||
defer frame.deinit();
|
||||
const client_inset = getClientInset(dpi);
|
||||
const bounding_client_size: XY(i32) = .{
|
||||
.x = (bounding_rect.right - bounding_rect.left) - client_inset.x,
|
||||
|
@ -1364,7 +1422,7 @@ fn getClientInset(dpi: u32) XY(i32) {
|
|||
0,
|
||||
window_style_ex,
|
||||
dpi,
|
||||
)) fatalWin32(
|
||||
)) win32.panicWin32(
|
||||
"AdjustWindowRect",
|
||||
win32.GetLastError(),
|
||||
);
|
||||
|
@ -1392,5 +1450,5 @@ fn setWindowPosRect(hwnd: win32.HWND, rect: win32.RECT) void {
|
|||
rect.right - rect.left,
|
||||
rect.bottom - rect.top,
|
||||
.{ .NOZORDER = 1 },
|
||||
)) fatalWin32("SetWindowPos", win32.GetLastError());
|
||||
)) win32.panicWin32("SetWindowPos", win32.GetLastError());
|
||||
}
|
||||
|
|
|
@ -1,17 +1,6 @@
|
|||
const std = @import("std");
|
||||
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
|
||||
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;
|
||||
|
|
Loading…
Add table
Reference in a new issue