280 lines
9.0 KiB
Zig
280 lines
9.0 KiB
Zig
pub const Tab = struct {
|
|
allocator: std.mem.Allocator,
|
|
currentLocation: ?*Container,
|
|
currentItems: locked(?std.ArrayList(Arc(*Item))),
|
|
currentLocationChanged: Observable(?*Container),
|
|
currentItemFullName: ?models.FullName,
|
|
currentItem: ?Arc(*models.Item),
|
|
currentRequested: ?union(enum) { item: Arc(*models.Item), name: models.FullName },
|
|
itemComparer: *const fn (item1: *const models.Item, item2: *const models.Item) std.math.Order,
|
|
threadPool: *std.Thread.Pool,
|
|
rootProvider: *RootProvider,
|
|
|
|
pub fn init(
|
|
self: *Tab,
|
|
threadPool: *std.Thread.Pool,
|
|
rootProvider: *RootProvider,
|
|
allocator: std.mem.Allocator,
|
|
) void {
|
|
self.* = Tab{
|
|
.allocator = allocator,
|
|
.currentItems = .{ .data = null },
|
|
.currentLocationChanged = Observable(?*Container).init(allocator),
|
|
.currentLocation = null,
|
|
.currentRequested = null,
|
|
.currentItem = null,
|
|
.currentItemFullName = null,
|
|
.itemComparer = order.orderByDisplayName,
|
|
.threadPool = threadPool,
|
|
.rootProvider = rootProvider,
|
|
};
|
|
}
|
|
|
|
pub fn setCurrentLocation(self: *Tab, newLocationFullName: models.FullName) !void {
|
|
if (self.currentLocation) |c| {
|
|
c.item.deinit();
|
|
}
|
|
|
|
self.cleanCurrentItem();
|
|
|
|
const newLocationContainer = blk: {
|
|
const newLocation = try self.rootProvider.getItemByFullName(newLocationFullName, &.{}, self.allocator);
|
|
errdefer {
|
|
newLocation.deinit();
|
|
self.currentLocation = null;
|
|
self.currentLocationChanged.notify(null);
|
|
}
|
|
|
|
const newLocationContainer = switch (newLocation.item) {
|
|
.container => |c| c,
|
|
.element => return error.NotContainer,
|
|
};
|
|
|
|
self.currentLocation = newLocationContainer;
|
|
break :blk newLocationContainer;
|
|
};
|
|
self.currentLocationChanged.notify(newLocationContainer);
|
|
|
|
self.updateCurrentItem() catch {
|
|
self.currentItemFullName = null;
|
|
self.currentItem = null;
|
|
};
|
|
|
|
//TODO: Proper error handling
|
|
std.Thread.Pool.spawn(self.threadPool, loadItemsWrapper, .{ self, newLocationContainer }) catch unreachable;
|
|
}
|
|
|
|
pub fn selectItem(self: *Tab, requested_selected: models.FullName) !void {
|
|
self.currentItems.mutex.lock();
|
|
defer self.currentItems.mutex.unlock();
|
|
|
|
self.cleanCurrentRequested();
|
|
|
|
self.currentRequested = .{
|
|
.name = models.FullName{
|
|
.path = try self.allocator.dupe(u8, requested_selected.path),
|
|
},
|
|
};
|
|
|
|
return self.updateCurrentItem();
|
|
}
|
|
|
|
fn cleanCurrentRequested(self: *Tab) void {
|
|
const requested = self.currentRequested orelse return;
|
|
|
|
switch (requested) {
|
|
.item => |item| {
|
|
if (item.releaseUnwrap()) |i| i.deinit();
|
|
},
|
|
.name => |fullName| self.allocator.free(fullName.path),
|
|
}
|
|
|
|
self.currentRequested = null;
|
|
}
|
|
|
|
fn updateCurrentItem(self: *Tab) !void {
|
|
const currentRequested = self.currentRequested orelse return;
|
|
|
|
const requestedFullName = switch (currentRequested) {
|
|
.item => |*item| item.value.*.fullName,
|
|
.name => |fullName| fullName,
|
|
};
|
|
// The requested is already selected
|
|
if (self.currentItemFullName) |currentItem| {
|
|
if (std.mem.order(u8, requestedFullName.path, currentItem.path) == .eq) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
const currentItems = self.currentItems.data orelse return error.Error;
|
|
if (currentItems.items.len == 0) return error.Error;
|
|
|
|
// Looking for the requested
|
|
const foundIndex = index: for (0..currentItems.items.len) |i| {
|
|
const arc_item = currentItems.items[i];
|
|
const arc_copy = arc_item.retain();
|
|
defer if (arc_copy.releaseUnwrap()) |item| item.deinit();
|
|
|
|
switch (currentRequested) {
|
|
.item => |*itemArc| {
|
|
const comp = self.itemComparer(itemArc.value.*, arc_copy.value.*);
|
|
if (comp == .eq) {
|
|
break :index i;
|
|
}
|
|
if (comp == .lt) {
|
|
break :index i -| 1;
|
|
}
|
|
},
|
|
.name => |fullName| {
|
|
if (std.mem.order(u8, fullName.path, arc_copy.value.*.fullName.path) == .eq) {
|
|
break :index i;
|
|
}
|
|
},
|
|
}
|
|
} else null;
|
|
|
|
if (foundIndex) |i| {
|
|
const arc_item = currentItems.items[i];
|
|
|
|
self.cleanCurrentRequested();
|
|
self.currentRequested = .{
|
|
.item = arc_item.retain(),
|
|
};
|
|
|
|
try self.setSelectedItemInner(arc_item.retain());
|
|
|
|
return;
|
|
}
|
|
|
|
//TODO: use lastdeepestselectedpath
|
|
|
|
// Fallback to first item
|
|
{
|
|
const arc_item = currentItems.items[0];
|
|
|
|
try self.setSelectedItemInner(arc_item.retain());
|
|
}
|
|
}
|
|
|
|
fn setSelectedItemInner(self: *Tab, newSelectedItemArc: Arc(*models.Item)) !void {
|
|
const arc = newSelectedItemArc.retain();
|
|
defer if (arc.releaseUnwrap()) |item| item.deinit();
|
|
|
|
self.cleanCurrentItem();
|
|
|
|
self.currentItem = newSelectedItemArc;
|
|
self.currentItemFullName = models.FullName{
|
|
.path = try self.allocator.dupe(u8, arc.value.*.fullName.path),
|
|
};
|
|
}
|
|
|
|
fn loadItemsWrapper(self: *Tab, location: *Container) void {
|
|
loadItems(self, location) catch return;
|
|
}
|
|
fn loadItems(self: *Tab, location: *Container) !void {
|
|
{
|
|
self.currentItems.mutex.lock();
|
|
defer self.currentItems.mutex.unlock();
|
|
|
|
const data = self.currentItems.data;
|
|
self.currentItems.data = null;
|
|
if (data) |currentItems| {
|
|
for (currentItems.items) |*item| {
|
|
if (item.releaseUnwrap()) |i| i.deinit();
|
|
}
|
|
currentItems.deinit();
|
|
}
|
|
}
|
|
|
|
{
|
|
self.currentItems.mutex.lock();
|
|
defer self.currentItems.mutex.unlock();
|
|
|
|
self.currentItems.data = std.ArrayList(Arc(*Item)).init(self.allocator);
|
|
}
|
|
errdefer {
|
|
self.currentItems.data.?.deinit();
|
|
self.currentItems.data = null;
|
|
}
|
|
|
|
//TODO: add async
|
|
while (location.childrenLoading) {
|
|
std.Thread.sleep(1 * std.time.ns_per_ms);
|
|
}
|
|
|
|
for (location.children.items) |item_fullname| {
|
|
const resolvedItem = location.item.provider.getItemByFullName(item_fullname, &.{ .skipChildInit = true }, self.allocator) catch |e| {
|
|
//TODO: save error to container errors
|
|
std.debug.print("error {} {s}\r\n", .{ e, item_fullname.path });
|
|
continue;
|
|
};
|
|
|
|
self.currentItems.mutex.lock();
|
|
defer self.currentItems.mutex.unlock();
|
|
|
|
const insertIndex = for (self.currentItems.data.?.items, 0..) |*item, i| {
|
|
if (order.orderByDisplayName(resolvedItem, item.value.*) == .lt) break i;
|
|
} else null;
|
|
|
|
const arc = try Arc(*Item).init(self.allocator, resolvedItem);
|
|
if (insertIndex) |i| {
|
|
try self.currentItems.data.?.insert(i, arc);
|
|
} else {
|
|
try self.currentItems.data.?.append(arc);
|
|
}
|
|
|
|
self.updateCurrentItem() catch {};
|
|
}
|
|
}
|
|
|
|
pub fn deinit(self: *Tab) void {
|
|
if (self.currentLocation) |c| {
|
|
c.item.deinit();
|
|
}
|
|
|
|
const data = self.currentItems.data;
|
|
self.currentItems.data = null;
|
|
if (data) |currentItems| {
|
|
for (currentItems.items) |arc_item| {
|
|
if (arc_item.releaseUnwrap()) |item| {
|
|
item.deinit();
|
|
}
|
|
}
|
|
currentItems.deinit();
|
|
}
|
|
|
|
self.cleanCurrentItem();
|
|
|
|
self.cleanCurrentRequested();
|
|
|
|
self.allocator.destroy(self);
|
|
}
|
|
|
|
fn cleanCurrentItem(self: *Tab) void {
|
|
if (self.currentItemFullName) |currentItem| {
|
|
self.allocator.free(currentItem.path);
|
|
self.currentItemFullName = null;
|
|
}
|
|
|
|
if (self.currentItem) |currentItem| {
|
|
if (currentItem.releaseUnwrap()) |item| item.deinit();
|
|
self.currentItem = null;
|
|
}
|
|
}
|
|
};
|
|
|
|
const std = @import("std");
|
|
const assert = std.debug.assert;
|
|
const Mutex = std.Thread.Mutex;
|
|
|
|
const order = @import("./order.zig");
|
|
|
|
const models = @import("../models.zig");
|
|
const Container = models.Container;
|
|
const Item = models.Item;
|
|
|
|
const locked = @import("../sync.zig").locked;
|
|
const Observable = @import("../observable.zig").Observable;
|
|
const RootProvider = @import("../provider/root.zig").RootProvider;
|
|
const Arc = @import("zigrc").Arc;
|