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;