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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions plugins/workbench/src/Workbench.zig
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ workspaces: std.AutoArrayHashMapUnmanaged(u64, Workspace) = .empty,
/// plugin's own directory whenever it changes (`workbench_layout.savePanes`).
panes: Panes,
panes_dirty: bool = false,
/// The person's arrangement while a demo has the window (`holdPanes`), a copy of its own, and the
/// pane they were in: nothing is saved while it is held.
held_panes: ?Panes = null,
held_open: u64 = 0,
/// Where the panes are arranged when the app arranges them instead of `panes`: each pane a view
/// of the app's, in a place of the app's, beside another by a split of the app's (fizzy's
/// `Workspaces`, behind `FIZZY_WORKSPACES=1`). Null: the panes are this tree's leaves, as before.
Expand Down Expand Up @@ -387,6 +391,18 @@ pub fn rebuildWorkspaces(self: *Workbench) !void {
return workbench_layout.rebuildWorkspaces(self);
}

pub fn holdPanes(self: *Workbench) void {
workbench_layout.holdPanes(self);
}

pub fn onePane(self: *Workbench) void {
workbench_layout.onePane(self);
}

pub fn restorePanes(self: *Workbench) void {
workbench_layout.restorePanes(self);
}

pub fn forgetPanes(self: *Workbench) void {
workbench_layout.forgetPanes(self);
}
Expand Down
41 changes: 41 additions & 0 deletions plugins/workbench/src/workbench_layout.zig
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ pub fn drawWorkspaces(wb: *Workbench, index: usize) !dvui.App.Result {
/// no plugin directory to offer.
fn savePanes(wb: *Workbench) void {
wb.panes_dirty = false;
// A demo has the window: what it arranges is not the person's (`holdPanes`).
if (wb.held_panes != null) return;
if (comptime builtin.target.cpu.arch == .wasm32) return;
const path = panesPath(wb) orelse return;
defer wb.allocator.free(path);
Expand Down Expand Up @@ -214,7 +216,11 @@ fn loadPanes(wb: *Workbench) void {
loaded.deinit();
return;
}
adoptPanes(wb, loaded);
}

/// Make `loaded` the arrangement, every pane in it a workspace.
fn adoptPanes(wb: *Workbench, loaded: Panes) void {
wb.panes.deinit();
wb.panes = loaded;
for (wb.panes.nodes.items) |n| {
Expand All @@ -231,6 +237,41 @@ fn loadPanes(wb: *Workbench) void {
}
}

/// Keep the person's arrangement while a demo has the window, and save nothing until it is put
/// back (`restorePanes`): the demo's splits are not theirs. A second hold keeps the first.
pub fn holdPanes(wb: *Workbench) void {
if (wb.held_panes != null) return;
// A copy through a snapshot: a snapshot borrows the pane names from the live tree, which the
// demo's first cut replaces (`onePane`).
const snap = wb.panes.snapshot(wb.allocator) catch return;
defer snap.deinit(wb.allocator);
wb.held_panes = Panes.fromSnapshot(wb.allocator, snap) catch return;
wb.held_open = wb.open_workspace_grouping;
}

/// One pane, as a demo's keyframe declares the window: the open one, the others closing with
/// their leaves (`rebuildWorkspaces`). Its documents are the keyframe's to open.
pub fn onePane(wb: *Workbench) void {
var one = Panes.init(wb.allocator);
one.owns_panel_ids = true;
one.animated = true;
const root = one.allocNodeForRoot() catch return;
var buf: [32]u8 = undefined;
one.insertTabOwned(root, 0, Workspace.name(&buf, wb.open_workspace_grouping)) catch {
one.deinit();
return;
};
adoptPanes(wb, one);
}

/// The person's arrangement again (`holdPanes`), and saving with it.
pub fn restorePanes(wb: *Workbench) void {
const held = wb.held_panes orelse return;
wb.held_panes = null;
adoptPanes(wb, held);
if (wb.workspaces.contains(wb.held_open)) wb.open_workspace_grouping = wb.held_open;
}

/// Delete the saved arrangement: the app has taken it over (`Workbench.places`).
pub fn forgetPanes(wb: *Workbench) void {
if (comptime builtin.target.cpu.arch == .wasm32) return;
Expand Down
7 changes: 7 additions & 0 deletions src/editor/Demo.zig
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,8 @@ fn begin(ctx: *anyopaque, _: *const automation.Tape) void {
editor.flushLayout();
editor.flushSettings();
self.saveSettings();
// The document panes too: kept in memory, and none of the demo's saved over them.
editor.workbench.holdPanes();

const gpa = editor.app.gpa;
var docs: std.ArrayList(Saved.Doc) = .empty;
Expand Down Expand Up @@ -497,11 +499,14 @@ fn end(ctx: *anyopaque) void {
if (self.quitting) {
self.freeSaved();
self.freeSavedSettings();
editor.workbench.restorePanes();
return;
}
self.restoreSettings();
self.closeAllDocuments();
self.unmountFiles();
// Before the person's documents reopen: into the pane they were in.
editor.workbench.restorePanes();

// The layout as the user left it: `begin` flushed it, and nothing has been written since.
editor.app.layout.resetLayout(gpa);
Expand Down Expand Up @@ -568,6 +573,8 @@ fn keyframe(ctx: *anyopaque, kf: *const automation.Tape.Keyframe) void {

editor.command_palette.finishClose();
self.closeAllDocuments();
// One pane: a split a later moment made is gone at a cut, as on the first pass.
editor.workbench.onePane();
self.applySettings(kf.settings);

// Fresh bytes every time: a replay must not see what the last pass typed or saved.
Expand Down
4 changes: 2 additions & 2 deletions src/editor/Editor.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4471,8 +4471,8 @@ pub fn tick(editor: *Editor) !dvui.App.Result {
{
const t = fizzy.core.hitch.begin(.rebuild_workspaces);
defer t.end();
editor.rebuildWorkspaces() catch {
dvui.log.err("Failed to rebuild workspaces", .{});
editor.rebuildWorkspaces() catch |err| {
dvui.log.err("Failed to rebuild workspaces: {t}", .{err});
};
// The first rebuild reopened last session's tabs; what the session held beyond them is
// kept in recovery files. A frame on, the panes have drawn, and each shows the tab it did.
Expand Down
40 changes: 40 additions & 0 deletions tests/demos.zig
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,29 @@ test "demo: every bundled demo plays to the end through fizzy's stage, a seek la
while (p.state == .seeking and frames < 400) : (frames += 1) _ = try dvui.testing.step(headlessFrame);
try std.testing.expect(p.seq.now >= @as(f64, @floatFromInt(second)));

// A demo's panes are its own: a cut is one pane again, and the end gives back the person's.
{
const wb = &app.editor.workbench;
p.unload();
try dvui.testing.settle(headlessFrame);
const panes_before = wb.workspaces.count();
const split = try splitDemo();
defer gpa.free(split);
try app.editor.demo.playTape(split);
frames = 0;
while (p.state != .ended and frames < 2_000) : (frames += 1) _ = try dvui.testing.step(headlessFrame);
try std.testing.expectEqual(@as(u32, 0), p.timeouts);
try std.testing.expectEqual(@as(usize, 2), wb.workspaces.count());
p.seek(0);
frames = 0;
while ((p.state == .seeking or wb.workspaces.count() != 1) and frames < 400) : (frames += 1) _ = try dvui.testing.step(headlessFrame);
try std.testing.expectEqual(@as(usize, 1), wb.workspaces.count());
p.unload();
try dvui.testing.settle(headlessFrame);
try std.testing.expectEqual(panes_before, wb.workspaces.count());
try std.testing.expect(wb.held_panes == null);
}

// A tape loaded some other way is not the one the service started.
try app.editor.demo.play("markdown");
try std.testing.expectEqual(@as(?[]const u8, null), api.current());
Expand Down Expand Up @@ -220,6 +243,23 @@ fn openZipFromBytes(editor: *fizzy.Editor, name: []const u8, files: []const stru
return gpa.dupe(u8, folder);
}

/// A demo that drags its second tab onto the editor's right edge, opening a pane beside, encoded.
fn splitDemo() ![]u8 {
const gpa = std.testing.allocator;
var s: automation.Script = .init(gpa, "split", "Split");
errdefer s.deinit();
try s.keyframe(.{
.root = "demo://split",
.files = &.{ .{ .path = "a.md", .text = "# A\n" }, .{ .path = "b.md", .text = "# B\n" } },
.open = &.{ "a.md", "b.md" },
});
try s.drag(.{ .tag = "workbench.tab:demo://split/b.md" }, .{ .tag = "region:Main", .dx = 132 }, .{});
s.pause(600);
var owned = try s.finish();
defer owned.deinit();
return automation.binary.encode(gpa, owned.tape, .{});
}

/// A tape a plugin might register, encoded: a demo opening on a keyframe of one file, in two
/// chapters, or (`demo` false) a live tape of the same pauses, which a demo list leaves out.
fn encodedDemo(name: []const u8, demo: bool) ![]u8 {
Expand Down
Loading