feat(core): appstate
This commit is contained in:
4
src/core/action/action.zig
Normal file
4
src/core/action/action.zig
Normal file
@@ -0,0 +1,4 @@
|
||||
pub const Action = union(enum) {
|
||||
GoUp,
|
||||
Enter,
|
||||
};
|
||||
17
src/core/action/action_handler.zig
Normal file
17
src/core/action/action_handler.zig
Normal file
@@ -0,0 +1,17 @@
|
||||
pub fn handle(action: Action, appState: *AppState) !void {
|
||||
switch (action) {
|
||||
.GoUp => {
|
||||
if (appState.currentTab.currentLocation) |currentLocation| {
|
||||
const parent = currentLocation.item.parent;
|
||||
if (parent) |p| {
|
||||
try appState.currentTab.setCurrentLocation(.{ .path = p.path.path });
|
||||
} else return error.NoParent;
|
||||
} else return error.NoCurrentLocation;
|
||||
},
|
||||
.Enter => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
const Action = @import("action.zig").Action;
|
||||
const AppState = @import("../app_state.zig").AppState;
|
||||
const RootProvider = @import("../provider/root.zig").RootProvider;
|
||||
50
src/core/app_state.zig
Normal file
50
src/core/app_state.zig
Normal file
@@ -0,0 +1,50 @@
|
||||
pub const UnknownTabError = error.UnknownTab;
|
||||
|
||||
pub const AppState = struct {
|
||||
currentTab: *Tab = undefined,
|
||||
tabs: std.ArrayList(Tab),
|
||||
currentTabChanged: Observable(*Tab),
|
||||
tabPreCurrentItemsUnload: Observable(*Tab),
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) AppState {
|
||||
return .{
|
||||
.tabs = std.ArrayList(Tab).init(allocator),
|
||||
.currentTabChanged = Observable(*Tab).init(allocator),
|
||||
.tabPreCurrentItemsUnload = Observable(*Tab).init(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn addTab(self: *AppState, tab: Tab) void {
|
||||
self.tabs.append(tab);
|
||||
// tab.preCurrentItemsUnload.attach(.{
|
||||
// .ctx = @ptrCast(@alignCast(self)),
|
||||
// .update = preCurrentItemsUnload,
|
||||
// });
|
||||
}
|
||||
|
||||
pub fn setCurrentTab(self: *AppState, newTab: *Tab) !void {
|
||||
blk: {
|
||||
for (self.tabs.items) |item| {
|
||||
if (item == newTab) break :blk;
|
||||
}
|
||||
return UnknownTabError;
|
||||
}
|
||||
|
||||
self.currentTab = newTab;
|
||||
self.currentTabChanged.notify(newTab);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *AppState) void {
|
||||
self.currentTabChanged.deinit();
|
||||
self.tabs.deinit();
|
||||
}
|
||||
|
||||
fn preCurrentItemsUnload(ctx: *anyopaque, tab: *Tab) void {
|
||||
const self: AppState = @ptrCast(@alignCast(ctx));
|
||||
self.tabPreCurrentItemsUnload.notify(tab);
|
||||
}
|
||||
};
|
||||
|
||||
const std = @import("std");
|
||||
const Tab = @import("tab/tab.zig").Tab;
|
||||
const Observable = @import("observable.zig").Observable;
|
||||
@@ -54,18 +54,26 @@ pub const Container = struct {
|
||||
self.item.allocator.free(itemFullName.path);
|
||||
}
|
||||
self.children.deinit();
|
||||
self.item.allocator.destroy(self);
|
||||
}
|
||||
};
|
||||
|
||||
pub const FullName = struct {
|
||||
path: []const u8,
|
||||
|
||||
pub fn getChild(self: *FullName, childName: []const u8, allocator: std.mem.Allocator) !FullName {
|
||||
pub fn getChild(self: *const FullName, childName: []const u8, allocator: std.mem.Allocator) !FullName {
|
||||
const path = try std.fmt.allocPrint(allocator, "{s}{s}{s}", .{ self.path, consts.PathSeparator, childName });
|
||||
|
||||
return FullName{ .path = path };
|
||||
}
|
||||
|
||||
pub fn getParent(self: *const FullName, allocator: std.mem.Allocator) !?FullName {
|
||||
const lastIndex = std.mem.lastIndexOf(u8, self.path, consts.PathSeparator) orelse return null;
|
||||
if (lastIndex == 0) return null;
|
||||
|
||||
const path = try std.fmt.allocPrint(allocator, "{s}", .{self.path[0..lastIndex]});
|
||||
|
||||
return FullName{ .path = path };
|
||||
}
|
||||
};
|
||||
|
||||
pub const NativePath = struct {
|
||||
@@ -73,7 +81,7 @@ pub const NativePath = struct {
|
||||
};
|
||||
|
||||
pub const AbsolutePath = struct {
|
||||
path: []const u8,
|
||||
path: FullName,
|
||||
type: AbsolutePathType,
|
||||
};
|
||||
|
||||
@@ -81,7 +89,7 @@ pub const Error = struct {
|
||||
content: []const u8,
|
||||
};
|
||||
|
||||
pub const AbsolutePathType = enum {
|
||||
Container,
|
||||
Item,
|
||||
pub const AbsolutePathType = union(enum) {
|
||||
container,
|
||||
item,
|
||||
};
|
||||
|
||||
41
src/core/observable.zig
Normal file
41
src/core/observable.zig
Normal file
@@ -0,0 +1,41 @@
|
||||
pub fn Observable(T: type) type {
|
||||
return struct {
|
||||
observers: std.ArrayList(*const Observer(T)),
|
||||
|
||||
const Self = @This();
|
||||
pub fn init(allocator: std.mem.Allocator) Self {
|
||||
return .{
|
||||
.observers = std.ArrayList(*const Observer(T)).init(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.observers.deinit();
|
||||
}
|
||||
|
||||
pub fn attach(self: *Self, obs: *const Observer(T)) !void {
|
||||
try self.observers.append(obs);
|
||||
}
|
||||
|
||||
pub fn detach(self: *Self, obs: *const Observer(T)) void {
|
||||
if (std.mem.indexOfScalar(*const Observer, self.observers.items, obs)) |index| {
|
||||
_ = self.observers.swapRemove(index);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify(self: Self, args: T) void {
|
||||
for (self.observers.items) |obs| {
|
||||
obs.update(obs.ctx, args);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn Observer(T: type) type {
|
||||
return struct {
|
||||
ctx: *anyopaque,
|
||||
update: *const fn (ctx: *anyopaque, args: T) void,
|
||||
};
|
||||
}
|
||||
|
||||
const std = @import("std");
|
||||
@@ -1,15 +1,4 @@
|
||||
const std = @import("std");
|
||||
const models = @import("../models.zig");
|
||||
const Provider = @import("provider.zig").Provider;
|
||||
const ProviderVTable = @import("provider.zig").VTable;
|
||||
const GetItemsError = @import("provider.zig").GetItemsError;
|
||||
const InitContext = @import("provider.zig").InitContext;
|
||||
|
||||
const FullName = models.FullName;
|
||||
const Item = models.Item;
|
||||
const ItemEnum = models.ItemEnum;
|
||||
const Element = models.Element;
|
||||
const Container = models.Container;
|
||||
pub const LocalProviderId = "local";
|
||||
|
||||
// TODO: the container might be freed while this runs
|
||||
// Tab should hold something at pass it here
|
||||
@@ -18,6 +7,7 @@ fn loadChildren(container: *Container) void {
|
||||
container.childrenLoading = false;
|
||||
}
|
||||
|
||||
std.debug.print("load children {s}", .{container.item.nativePath.path});
|
||||
var dir = std.fs.cwd().openDir(container.item.nativePath.path, .{ .iterate = true }) catch {
|
||||
// const errorContent = std.fmt.allocPrint(container.item.allocator, "Could not open directory '{s}'.", .{container.item.nativePath.path}) catch return;
|
||||
// container.item.errors.append(.{ .content = errorContent }) catch return;
|
||||
@@ -35,6 +25,8 @@ fn loadChildren(container: *Container) void {
|
||||
|
||||
const VTable: ProviderVTable = .{
|
||||
.getItemByFullName = getItemByFullNameGeneric,
|
||||
.canHandle = canHandleGeneric,
|
||||
.deinit = deinitGeneric,
|
||||
};
|
||||
|
||||
pub fn getItemByFullNameGeneric(
|
||||
@@ -47,6 +39,43 @@ pub fn getItemByFullNameGeneric(
|
||||
return self.getItemByFullName(fullName, initContext, allocator);
|
||||
}
|
||||
|
||||
pub fn canHandleGeneric(
|
||||
context: *anyopaque,
|
||||
fullName: FullName,
|
||||
) bool {
|
||||
const self: *LocalContentProvider = @ptrCast(@alignCast(context));
|
||||
return self.canHandle(fullName);
|
||||
}
|
||||
|
||||
pub fn deinitGeneric(_: *anyopaque) void {}
|
||||
|
||||
pub fn getFullNameByNativePath(allocator: std.mem.Allocator, nativePath: NativePath) !FullName {
|
||||
const correct_sep = try std.mem.replaceOwned(u8, allocator, nativePath.path, std.fs.path.sep_str, "/");
|
||||
defer allocator.free(correct_sep);
|
||||
|
||||
const asd = if (std.mem.startsWith(u8, correct_sep, "/"))
|
||||
correct_sep[1..]
|
||||
else
|
||||
correct_sep;
|
||||
|
||||
const full_name_path = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ LocalProviderId, asd });
|
||||
|
||||
return .{ .path = full_name_path };
|
||||
}
|
||||
|
||||
pub fn getNativePathByFullName(allocator: std.mem.Allocator, fullName: FullName) ![]u8 {
|
||||
const fullNameWithoutId = fullName.path[LocalProviderId.len + 1 ..];
|
||||
var native_path = try std.mem.replaceOwned(u8, allocator, fullNameWithoutId, "/", std.fs.path.sep_str);
|
||||
|
||||
if (native_os == .linux) {
|
||||
const linux_native_path = try std.fmt.allocPrint(allocator, "/{s}", .{native_path});
|
||||
allocator.free(native_path);
|
||||
native_path = linux_native_path;
|
||||
}
|
||||
|
||||
return native_path;
|
||||
}
|
||||
|
||||
pub const LocalContentProvider = struct {
|
||||
threadPool: *std.Thread.Pool,
|
||||
|
||||
@@ -56,7 +85,7 @@ pub const LocalContentProvider = struct {
|
||||
initContext: *const InitContext,
|
||||
allocator: std.mem.Allocator,
|
||||
) GetItemsError!*Item {
|
||||
const native_path = try std.mem.replaceOwned(u8, allocator, fullName.path, "/", std.fs.path.sep_str);
|
||||
const native_path = try getNativePathByFullName(allocator, fullName);
|
||||
defer allocator.free(native_path);
|
||||
|
||||
const kind: union(enum) { directory, file } = blk: {
|
||||
@@ -139,7 +168,10 @@ pub const LocalContentProvider = struct {
|
||||
const name = try allocator.dupe(u8, basename);
|
||||
const displayName = try allocator.dupe(u8, basename);
|
||||
const fullName2 = try allocator.dupe(u8, fullName.path);
|
||||
const nativePath = try allocator.dupe(u8, fullName.path);
|
||||
const nativePath = try getNativePathByFullName(allocator, fullName);
|
||||
|
||||
const parentFullName = try fullName.getParent(allocator);
|
||||
const parent = if (parentFullName) |p| models.AbsolutePath{ .path = p, .type = .container } else null;
|
||||
|
||||
item.* = Item{
|
||||
.allocator = allocator,
|
||||
@@ -148,12 +180,19 @@ pub const LocalContentProvider = struct {
|
||||
.displayName = displayName,
|
||||
.fullName = .{ .path = fullName2 },
|
||||
.nativePath = models.NativePath{ .path = nativePath },
|
||||
.parent = null,
|
||||
.parent = parent,
|
||||
.item = innerItem,
|
||||
.errors = std.ArrayList(models.Error).init(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn canHandle(
|
||||
_: *LocalContentProvider,
|
||||
fullName: FullName,
|
||||
) bool {
|
||||
return std.mem.startsWith(u8, fullName.path, "local/");
|
||||
}
|
||||
|
||||
pub fn provider(self: *LocalContentProvider) Provider {
|
||||
return Provider{
|
||||
.object = self,
|
||||
@@ -161,3 +200,18 @@ pub const LocalContentProvider = struct {
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const std = @import("std");
|
||||
const models = @import("../models.zig");
|
||||
const Provider = @import("provider.zig").Provider;
|
||||
const ProviderVTable = @import("provider.zig").VTable;
|
||||
const GetItemsError = @import("provider.zig").GetItemsError;
|
||||
const InitContext = @import("provider.zig").InitContext;
|
||||
|
||||
const FullName = models.FullName;
|
||||
const NativePath = models.NativePath;
|
||||
const Item = models.Item;
|
||||
const ItemEnum = models.ItemEnum;
|
||||
const Element = models.Element;
|
||||
const Container = models.Container;
|
||||
const native_os = @import("builtin").os.tag;
|
||||
|
||||
@@ -5,6 +5,13 @@ pub const VTable = struct {
|
||||
initContext: *const InitContext,
|
||||
allocator: std.mem.Allocator,
|
||||
) GetItemsError!*Item,
|
||||
|
||||
canHandle: *const fn (
|
||||
self: *anyopaque,
|
||||
fullName: FullName,
|
||||
) bool,
|
||||
|
||||
deinit: *const fn (self: *anyopaque) void,
|
||||
};
|
||||
|
||||
pub const GetItemsError = error{
|
||||
@@ -25,6 +32,17 @@ pub const Provider = struct {
|
||||
) GetItemsError!*Item {
|
||||
return self.vtable.getItemByFullName(self.object, fullName, initContext, allocator);
|
||||
}
|
||||
|
||||
pub inline fn canHandle(
|
||||
self: *const Provider,
|
||||
fullName: FullName,
|
||||
) bool {
|
||||
return self.vtable.canHandle(self.object, fullName);
|
||||
}
|
||||
|
||||
pub inline fn deinit(self: *const Provider) void {
|
||||
return self.vtable.deinit(self.object);
|
||||
}
|
||||
};
|
||||
|
||||
pub const InitContext = struct {
|
||||
|
||||
45
src/core/provider/root.zig
Normal file
45
src/core/provider/root.zig
Normal file
@@ -0,0 +1,45 @@
|
||||
pub const RootGetItemsError = GetItemsError || error{ProviderNotFound};
|
||||
pub const RootProvider = struct {
|
||||
providers: std.ArrayList(*Provider),
|
||||
pub fn init(allocator: std.mem.Allocator) RootProvider {
|
||||
return .{
|
||||
.providers = std.ArrayList(*Provider).init(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getItemByFullName(
|
||||
self: *const RootProvider,
|
||||
fullName: FullName,
|
||||
initContext: *const InitContext,
|
||||
allocator: std.mem.Allocator,
|
||||
) RootGetItemsError!*Item {
|
||||
const provider = for (self.providers.items) |provider| {
|
||||
if (provider.canHandle(fullName)) {
|
||||
break provider;
|
||||
}
|
||||
} else return error.ProviderNotFound;
|
||||
|
||||
return provider.getItemByFullName(fullName, initContext, allocator);
|
||||
}
|
||||
|
||||
pub fn addProvider(self: *Provider, provider: *Provider) !void {
|
||||
try self.providers.append(provider);
|
||||
}
|
||||
|
||||
pub fn deinit(
|
||||
self: *const RootProvider,
|
||||
) void {
|
||||
for (self.providers.items) |p| {
|
||||
p.deinit();
|
||||
}
|
||||
self.providers.deinit();
|
||||
}
|
||||
};
|
||||
|
||||
const std = @import("std");
|
||||
const Provider = @import("./provider.zig").Provider;
|
||||
const InitContext = @import("./provider.zig").InitContext;
|
||||
const GetItemsError = @import("./provider.zig").GetItemsError;
|
||||
const models = @import("../models.zig");
|
||||
const FullName = models.FullName;
|
||||
const Item = models.Item;
|
||||
@@ -1,19 +1,12 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const Mutex = std.Thread.Mutex;
|
||||
|
||||
const models = @import("../models.zig");
|
||||
const Container = models.Container;
|
||||
const Item = models.Item;
|
||||
|
||||
const locked = @import("../sync.zig").locked;
|
||||
|
||||
pub const Tab = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
currentLocation: ?*Container,
|
||||
currentItems: locked(?std.ArrayList(*Item)),
|
||||
currentLocationChanged: Observable(?*Container),
|
||||
currentItemsChanged: bool = false,
|
||||
preCurrentItemsUnload: Observable(*Tab),
|
||||
threadPool: *std.Thread.Pool,
|
||||
rootProvider: *RootProvider,
|
||||
_private: Private,
|
||||
|
||||
const Private = struct {
|
||||
@@ -23,44 +16,67 @@ pub const Tab = struct {
|
||||
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,
|
||||
.threadPool = threadPool,
|
||||
.rootProvider = rootProvider,
|
||||
.preCurrentItemsUnload = Observable(*Tab).init(allocator),
|
||||
._private = .{
|
||||
.currentItemsAllocator = std.heap.ArenaAllocator.init(allocator),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn setCurrentLocation(self: *Tab, newLocation: *Container) void {
|
||||
pub fn setCurrentLocation(self: *Tab, newLocationFullName: models.FullName) !void {
|
||||
if (self.currentLocation) |c| {
|
||||
c.item.deinit();
|
||||
self.allocator.destroy(c);
|
||||
}
|
||||
|
||||
self.currentLocation = newLocation;
|
||||
errdefer {
|
||||
self.currentLocation = null;
|
||||
self.currentLocationChanged.notify(null);
|
||||
}
|
||||
|
||||
const newLocation = try self.rootProvider.getItemByFullName(newLocationFullName, &.{}, self.allocator);
|
||||
|
||||
const newLocationContainer = switch (newLocation.item) {
|
||||
.container => |c| c,
|
||||
.element => return error.NotContainer,
|
||||
};
|
||||
|
||||
self.currentLocation = newLocationContainer;
|
||||
self.currentLocationChanged.notify(newLocationContainer);
|
||||
|
||||
//TODO: Proper error handling
|
||||
std.Thread.Pool.spawn(self.threadPool, loadItemsWrapper, .{ self, newLocation }) catch unreachable;
|
||||
std.Thread.Pool.spawn(self.threadPool, loadItemsWrapper, .{ self, newLocationContainer }) catch unreachable;
|
||||
}
|
||||
|
||||
fn loadItemsWrapper(self: *Tab, location: *Container) void {
|
||||
loadItems(self, location) catch return;
|
||||
}
|
||||
fn loadItems(self: *Tab, location: *Container) !void {
|
||||
_ = self._private.currentItemsAllocator.reset(.retain_capacity);
|
||||
|
||||
{
|
||||
self.currentItems.mutex.lock();
|
||||
defer self.currentItems.mutex.unlock();
|
||||
|
||||
self.preCurrentItemsUnload.notify(self);
|
||||
|
||||
if (self.currentItems.data) |items| {
|
||||
items.deinit();
|
||||
}
|
||||
|
||||
self.currentItems.data = null;
|
||||
}
|
||||
|
||||
_ = self._private.currentItemsAllocator.reset(.retain_capacity);
|
||||
|
||||
const arenaAllocator = &self._private.currentItemsAllocator;
|
||||
const arena = arenaAllocator.allocator();
|
||||
|
||||
@@ -90,7 +106,7 @@ pub const Tab = struct {
|
||||
for (location.children.items) |item| {
|
||||
const resolvedItem = location.item.provider.getItemByFullName(item, &.{ .skipChildInit = true }, allocator) catch |e| {
|
||||
//TODO: save error to container errors
|
||||
std.debug.print("error {} {s}\r\n", .{e, item.path});
|
||||
std.debug.print("error {} {s}\r\n", .{ e, item.path });
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -98,6 +114,11 @@ pub const Tab = struct {
|
||||
defer self.currentItems.mutex.unlock();
|
||||
|
||||
try self.currentItems.data.?.append(resolvedItem);
|
||||
}
|
||||
|
||||
{
|
||||
self.currentItems.mutex.lock();
|
||||
defer self.currentItems.mutex.unlock();
|
||||
self.currentItemsChanged = true;
|
||||
}
|
||||
}
|
||||
@@ -109,3 +130,15 @@ pub const Tab = struct {
|
||||
self._private.currentItemsAllocator.deinit();
|
||||
}
|
||||
};
|
||||
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const Mutex = std.Thread.Mutex;
|
||||
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user