From 7dd8e7c43f4c1da2a310dbd5d9c96c3a9de1d6e4 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 4 Dec 2024 12:48:00 -0800 Subject: [PATCH] remove unused file --- ' | 555 -------------------------------------------------------------- 1 file changed, 555 deletions(-) delete mode 100644 ' diff --git a/' b/' deleted file mode 100644 index 0b79f1879..000000000 --- a/' +++ /dev/null @@ -1,555 +0,0 @@ -const std = @import("std"); -const Allocator = std.mem.Allocator; -const assert = std.debug.assert; -const CircBuf = @import("../datastruct/main.zig").CircBuf; -const terminal = @import("main.zig"); -const point = terminal.point; -const Page = terminal.Page; -const PageList = terminal.PageList; -const Pin = PageList.Pin; -const Selection = terminal.Selection; -const Screen = terminal.Screen; - -pub const PageListSearch = struct { - alloc: Allocator, - - /// The list we're searching. - list: *PageList, - - /// The search term we're searching for. - needle: []const u8, - - /// The window is our sliding window of pages that we're searching so - /// we can handle boundary cases where a needle is partially on the end - /// of one page and the beginning of the next. - /// - /// Note that we're not guaranteed to straddle exactly two pages. If - /// the needle is large enough and/or the pages are small enough then - /// the needle can straddle N pages. Additionally, pages aren't guaranteed - /// to be equal size so we can't precompute the window size. - window: SlidingWindow, - - pub fn init( - alloc: Allocator, - list: *PageList, - needle: []const u8, - ) !PageListSearch { - var window = try CircBuf.init(alloc, 0); - errdefer window.deinit(); - - return .{ - .alloc = alloc, - .list = list, - .current = list.pages.first, - .needle = needle, - .window = window, - }; - } - - pub fn deinit(self: *PageListSearch) void { - _ = self; - - // TODO: deinit window - } -}; - -/// The sliding window of the pages we're searching. The window is always -/// big enough so that the needle can fit in it. -const SlidingWindow = struct { - /// The data buffer is a circular buffer of u8 that contains the - /// encoded page text that we can use to search for the needle. - data: DataBuf, - - /// The meta buffer is a circular buffer that contains the metadata - /// about the pages we're searching. This usually isn't that large - /// so callers must iterate through it to find the offset to map - /// data to meta. - meta: MetaBuf, - - /// The cursor into the data buffer for our current search. - i: usize = 0, - - const DataBuf = CircBuf(u8, 0); - const MetaBuf = CircBuf(Meta, undefined); - const Meta = struct { - node: *PageList.List.Node, - cell_map: Page.CellMap, - - pub fn deinit(self: *Meta) void { - self.cell_map.deinit(); - } - }; - - pub fn initEmpty(alloc: Allocator) Allocator.Error!SlidingWindow { - var data = try DataBuf.init(alloc, 0); - errdefer data.deinit(alloc); - - var meta = try MetaBuf.init(alloc, 0); - errdefer meta.deinit(alloc); - - return .{ - .data = data, - .meta = meta, - }; - } - - pub fn deinit(self: *SlidingWindow, alloc: Allocator) void { - self.data.deinit(alloc); - - var meta_it = self.meta.iterator(.forward); - while (meta_it.next()) |meta| meta.deinit(); - self.meta.deinit(alloc); - } - - /// Search the window for the next occurrence of the needle. - pub fn next(self: *SlidingWindow, needle: []const u8) void { - const slices = self.data.getPtrSlice(0, self.data.len()); - - // Search the first slice for the needle. - if (std.mem.indexOf(u8, slices[0][self.i..], needle)) |idx| { - // Found, map the match to a selection. - var meta_it = self.meta.iterator(.forward); - var i: usize = 0; - while (meta_it.next()) |meta| { - const meta_idx = idx - i; - if (meta.cell_map.items.len < meta_idx) { - // This meta doesn't contain the match. - i += meta.cell_map.items.len; - continue; - } - - // We found the meta that contains the start of the match. - const tl: PageList.Pin = tl: { - const map = meta.cell_map.items[meta_idx]; - break :tl .{ - .node = meta.node, - .y = map.y, - .x = map.x, - }; - }; - - _ = tl; - } - - // Found, we can move our index to the next character - // after the match. This let's us find all matches even if - // they overlap. - - self.i = idx + 1; - - @panic("TODO"); - } - } - - /// Return a selection for the given start and length into the data - /// buffer and also prune the data/meta buffers if possible up to - /// this start index. - fn selectAndPrune( - self: *SlidingWindow, - start: usize, - len: usize, - ) Selection { - assert(start < self.data.len()); - assert(start + len < self.data.len()); - - var meta_it = self.meta.iterator(.forward); - var meta_: ?Meta = meta_it.next(); - - // Find the start of the match - var offset: usize = 0; - var skip_nodes: usize = 0; - const tl: PageList.Pin = tl: { - while (meta_) |meta| : (meta_ = meta_it.next()) { - // meta_i is the index we expect to find the match in the - // cell map within this meta if it contains it. - const meta_i = start - offset; - if (meta_i >= meta.cell_map.items.len) { - // This meta doesn't contain the match. This means we - // can also prune this set of data because we only look - // forward. - offset += meta.cell_map.items.len; - skip_nodes += 1; - continue; - } - - // We found the meta that contains the start of the match. - const map = meta.cell_map.items[start]; - break :tl .{ - .node = meta.node, - .y = map.y, - .x = map.x, - }; - } - - // We never found the top-left. This is unreachable because - // we assert that the start index is within the data buffer, - // and when building the data buffer we assert the cell map - // length exactly matches the data buffer length. - unreachable; - }; - - // Keep track of the number of nodes we skipped for the tl. - const tl_skip_nodes = skip_nodes; - skip_nodes = 0; - - // Find the end of the match - const br: PageList.Pin = br: { - const end_idx = start + len - 1; - while (meta_) |meta| : (meta_ = meta_it.next()) { - const meta_i = end_idx - offset; - if (meta_i >= meta.cell_map.items.len) { - offset += meta.cell_map.items.len; - skip_nodes += 1; - continue; - } - - // We found the meta that contains the start of the match. - const map = meta.cell_map.items[end_idx]; - break :br .{ - .node = meta.node, - .y = map.y, - .x = map.x, - }; - } - }; - - // If we skipped any nodes for the bottom-right then we can prune - // all the way up to the total. If we didn't, it means we found - // the bottom-right in the same node as the top-left and we can't - // prune the node that the match is on because there may be - // more matches. - if (skip_nodes > 0) skip_nodes += tl_skip_nodes; - - _ = tl; - _ = br; - } - - /// Convert a data index into a pin. - fn pin( - self: *const SlidingWindow, - idx: usize, - it: ?*MetaBuf.Iterator, - ) struct { - /// The pin for the data index. - pin: Pin, - - /// The offset into the meta buffer that the pin was found. - /// This can be used to prune the meta buffer (its safe to prune - /// before this i). - meta_i: usize, - } { - _ = self; - _ = idx; - _ = start; - - while (it.next()) |meta| { - // meta_i is the index we expect to find the match in the - // cell map within this meta if it contains it. - const meta_i = start - offset; - if (meta_i >= meta.cell_map.items.len) { - // This meta doesn't contain the match. This means we - // can also prune this set of data because we only look - // forward. - offset += meta.cell_map.items.len; - skip_nodes += 1; - continue; - } - - // We found the meta that contains the start of the match. - const map = meta.cell_map.items[start]; - break :tl .{ - .node = meta.node, - .y = map.y, - .x = map.x, - }; - } - - } - - /// Add a new node to the sliding window. - /// - /// The window will prune itself if it can while always maintaining - /// the invariant that the `fixed_size` always fits within the window. - /// - /// Note it is possible for the window to be smaller than `fixed_size` - /// if not enough nodes have been added yet or the screen is just - /// smaller than the needle. - pub fn append( - self: *SlidingWindow, - alloc: Allocator, - node: *PageList.List.Node, - required_size: usize, - ) Allocator.Error!void { - // Initialize our metadata for the node. - var meta: Meta = .{ - .node = node, - .cell_map = Page.CellMap.init(alloc), - }; - errdefer meta.deinit(); - - // This is suboptimal but we need to encode the page once to - // temporary memory, and then copy it into our circular buffer. - // In the future, we should benchmark and see if we can encode - // directly into the circular buffer. - var encoded: std.ArrayListUnmanaged(u8) = .{}; - defer encoded.deinit(alloc); - - // Encode the page into the buffer. - const page: *const Page = &meta.node.data; - _ = page.encodeUtf8( - encoded.writer(alloc), - .{ .cell_map = &meta.cell_map }, - ) catch { - // writer uses anyerror but the only realistic error on - // an ArrayList is out of memory. - return error.OutOfMemory; - }; - assert(meta.cell_map.items.len == encoded.items.len); - - // Now that we know our buffer length, we can consider if we can - // prune our circular buffer or if we need to grow it. - prune: { - // Our buffer size after adding the new node. - const before_size: usize = self.data.len() + encoded.items.len; - - // Prune as long as removing the first (oldest) node retains - // our required size invariant. - var after_size: usize = before_size; - while (self.meta.first()) |oldest_meta| { - const new_size = after_size - oldest_meta.cell_map.items.len; - if (new_size < required_size) break :prune; - - // We can prune this node and retain our invariant. - // Update our new size, deinitialize the memory, and - // remove from the circular buffer. - after_size = new_size; - oldest_meta.deinit(); - self.meta.deleteOldest(1); - } - assert(after_size <= before_size); - - // If we didn't prune anything then we're done. - if (after_size == before_size) break :prune; - - // We need to prune our data buffer as well. - self.data.deleteOldest(before_size - after_size); - } - - // Ensure our buffers are big enough to store what we need. - try self.data.ensureUnusedCapacity(alloc, encoded.items.len); - try self.meta.ensureUnusedCapacity(alloc, 1); - - // Append our new node to the circular buffer. - try self.data.appendSlice(encoded.items); - try self.meta.append(meta); - - // Integrity check: verify our data matches our metadata exactly. - if (comptime std.debug.runtime_safety) { - var meta_it = self.meta.iterator(.forward); - var data_len: usize = 0; - while (meta_it.next()) |m| data_len += m.cell_map.items.len; - assert(data_len == self.data.len()); - } - } -}; - -test "SlidingWindow empty on init" { - const testing = std.testing; - const alloc = testing.allocator; - - var w = try SlidingWindow.initEmpty(alloc); - defer w.deinit(alloc); - try testing.expectEqual(0, w.data.len()); - try testing.expectEqual(0, w.meta.len()); -} - -test "SlidingWindow single append" { - const testing = std.testing; - const alloc = testing.allocator; - - var w = try SlidingWindow.initEmpty(alloc); - defer w.deinit(alloc); - - var s = try Screen.init(alloc, 80, 24, 0); - defer s.deinit(); - try s.testWriteString("hello. boo! hello. boo!"); - - // Imaginary needle for search - const needle = "boo!"; - - // We want to test single-page cases. - try testing.expect(s.pages.pages.first == s.pages.pages.last); - const node: *PageList.List.Node = s.pages.pages.first.?; - try w.append(alloc, node, needle.len); -} - -test "SlidingWindow two pages" { - const testing = std.testing; - const alloc = testing.allocator; - - var w = try SlidingWindow.initEmpty(alloc); - defer w.deinit(alloc); - - var s = try Screen.init(alloc, 80, 24, 1000); - defer s.deinit(); - - // Fill up the first page. The final bytes in the first page - // are "boo!" - const first_page_rows = s.pages.pages.first.?.data.capacity.rows; - for (0..first_page_rows - 1) |_| try s.testWriteString("\n"); - for (0..s.pages.cols - 4) |_| try s.testWriteString("x"); - try s.testWriteString("boo!"); - try testing.expect(s.pages.pages.first == s.pages.pages.last); - try s.testWriteString("\n"); - try testing.expect(s.pages.pages.first != s.pages.pages.last); - try s.testWriteString("hello. boo!"); - - // Imaginary needle for search - const needle = "boo!"; - - // Add both pages - const node: *PageList.List.Node = s.pages.pages.first.?; - try w.append(alloc, node, needle.len); - try w.append(alloc, node.next.?, needle.len); - - // Ensure our data is correct -} - -pub const PageSearch = struct { - alloc: Allocator, - node: *PageList.List.Node, - needle: []const u8, - cell_map: Page.CellMap, - encoded: std.ArrayListUnmanaged(u8) = .{}, - i: usize = 0, - - pub fn init( - alloc: Allocator, - node: *PageList.List.Node, - needle: []const u8, - ) !PageSearch { - var result: PageSearch = .{ - .alloc = alloc, - .node = node, - .needle = needle, - .cell_map = Page.CellMap.init(alloc), - }; - - const page: *const Page = &node.data; - _ = try page.encodeUtf8(result.encoded.writer(alloc), .{ - .cell_map = &result.cell_map, - }); - - return result; - } - - pub fn deinit(self: *PageSearch) void { - self.encoded.deinit(self.alloc); - self.cell_map.deinit(); - } - - pub fn next(self: *PageSearch) ?Selection { - // Search our haystack for the needle. The resulting index is - // the offset from self.i not the absolute index. - const haystack: []const u8 = self.encoded.items[self.i..]; - const i_offset = std.mem.indexOf(u8, haystack, self.needle) orelse { - self.i = self.encoded.items.len; - return null; - }; - - // Get our full index into the encoded buffer. - const idx = self.i + i_offset; - - // We found our search term. Move the cursor forward one beyond - // the match. This lets us find every repeated match. - self.i = idx + 1; - - const tl: PageList.Pin = tl: { - const map = self.cell_map.items[idx]; - break :tl .{ - .node = self.node, - .y = map.y, - .x = map.x, - }; - }; - const br: PageList.Pin = br: { - const map = self.cell_map.items[idx + self.needle.len - 1]; - break :br .{ - .node = self.node, - .y = map.y, - .x = map.x, - }; - }; - - return Selection.init(tl, br, false); - } -}; - -test "search single page one match" { - const testing = std.testing; - const alloc = testing.allocator; - - var s = try Screen.init(alloc, 80, 24, 0); - defer s.deinit(); - try s.testWriteString("hello, world"); - - // We want to test single-page cases. - try testing.expect(s.pages.pages.first == s.pages.pages.last); - const node: *PageList.List.Node = s.pages.pages.first.?; - - var it = try PageSearch.init(alloc, node, "world"); - defer it.deinit(); - - const sel = it.next().?; - try testing.expectEqual(point.Point{ .active = .{ - .x = 7, - .y = 0, - } }, s.pages.pointFromPin(.active, sel.start()).?); - try testing.expectEqual(point.Point{ .active = .{ - .x = 11, - .y = 0, - } }, s.pages.pointFromPin(.active, sel.end()).?); - - try testing.expect(it.next() == null); -} - -test "search single page multiple match" { - const testing = std.testing; - const alloc = testing.allocator; - - var s = try Screen.init(alloc, 80, 24, 0); - defer s.deinit(); - try s.testWriteString("hello. boo! hello. boo!"); - - // We want to test single-page cases. - try testing.expect(s.pages.pages.first == s.pages.pages.last); - const node: *PageList.List.Node = s.pages.pages.first.?; - - var it = try PageSearch.init(alloc, node, "boo!"); - defer it.deinit(); - - { - const sel = it.next().?; - try testing.expectEqual(point.Point{ .active = .{ - .x = 7, - .y = 0, - } }, s.pages.pointFromPin(.active, sel.start()).?); - try testing.expectEqual(point.Point{ .active = .{ - .x = 10, - .y = 0, - } }, s.pages.pointFromPin(.active, sel.end()).?); - } - { - const sel = it.next().?; - try testing.expectEqual(point.Point{ .active = .{ - .x = 19, - .y = 0, - } }, s.pages.pointFromPin(.active, sel.start()).?); - try testing.expectEqual(point.Point{ .active = .{ - .x = 22, - .y = 0, - } }, s.pages.pointFromPin(.active, sel.end()).?); - } - - try testing.expect(it.next() == null); -}