From 7d4d4190b836a8076bab684f68a14bc8caa21389 Mon Sep 17 00:00:00 2001 From: reugenio Date: Mon, 29 Dec 2025 10:23:32 +0100 Subject: [PATCH] refactor(advanced_table): Extraer result.zig y state_helpers.zig de state.zig MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Extraer AdvancedTableResult a result.zig (73 LOC) - Extraer funciones de map shifting a state_helpers.zig (141 LOC con tests) - Reducir state.zig de 1235 LOC a 1123 LOC - Añadir swapMapEntries para operaciones de move row - Mantener re-exports para compatibilidad --- src/widgets/advanced_table/result.zig | 73 +++++++++ src/widgets/advanced_table/state.zig | 152 +++---------------- src/widgets/advanced_table/state_helpers.zig | 141 +++++++++++++++++ 3 files changed, 234 insertions(+), 132 deletions(-) create mode 100644 src/widgets/advanced_table/result.zig create mode 100644 src/widgets/advanced_table/state_helpers.zig diff --git a/src/widgets/advanced_table/result.zig b/src/widgets/advanced_table/result.zig new file mode 100644 index 0000000..1fb1eee --- /dev/null +++ b/src/widgets/advanced_table/result.zig @@ -0,0 +1,73 @@ +//! AdvancedTable Result - Return type from advancedTable() call +//! +//! Struct independiente que contiene el resultado de una llamada a advancedTable(). +//! Extraído de state.zig para mejorar modularidad. + +const table_core = @import("../table_core/table_core.zig"); +const types = @import("types.zig"); + +pub const SortDirection = types.SortDirection; +pub const CRUDAction = types.CRUDAction; + +/// Result returned from advancedTable() call +pub const AdvancedTableResult = struct { + // Selection + selection_changed: bool = false, + selected_row: ?usize = null, + selected_col: ?usize = null, + + // Editing + edit_started: bool = false, + edit_ended: bool = false, + cell_edited: bool = false, + + // Sorting + sort_changed: bool = false, + sort_column: ?usize = null, + sort_direction: SortDirection = .none, + + // Row operations + row_inserted: bool = false, + row_deleted: bool = false, + row_moved: bool = false, + + // Auto-CRUD + crud_action: ?CRUDAction = null, + crud_success: bool = true, + + // Lookup (Phase 7) + lookup_success: ?bool = null, // null = no lookup, true = found, false = not found + + // Focus + clicked: bool = false, + + // ========================================================================= + // Edición CRUD Excel-style (simétrico con VirtualAdvancedTableResult) + // ========================================================================= + + /// Una fila fue completada (el usuario cambió de fila, tenía cambios pendientes) + row_committed: bool = false, + + /// ID de la fila que se hizo commit (índice en AdvancedTable) + row_commit_id: i64 = table_core.NEW_ROW_ID, + + /// Es un INSERT (ghost row) o UPDATE (fila existente) + row_commit_is_insert: bool = false, + + /// Cambios de la fila (válidos si row_committed = true) + row_changes: [table_core.MAX_PENDING_COLUMNS]table_core.PendingCellChange = undefined, + + /// Número de cambios en row_changes + row_changes_count: usize = 0, + + /// Tab presionado para salir del widget + tab_out: bool = false, + + /// Shift estaba presionado con Tab + tab_shift: bool = false, + + /// Obtiene los cambios como slice + pub fn getRowChanges(self: *const AdvancedTableResult) []const table_core.PendingCellChange { + return self.row_changes[0..self.row_changes_count]; + } +}; diff --git a/src/widgets/advanced_table/state.zig b/src/widgets/advanced_table/state.zig index 786b3b6..90682d6 100644 --- a/src/widgets/advanced_table/state.zig +++ b/src/widgets/advanced_table/state.zig @@ -6,6 +6,8 @@ const std = @import("std"); const types = @import("types.zig"); const schema_mod = @import("schema.zig"); const table_core = @import("../table_core/table_core.zig"); +const state_helpers = @import("state_helpers.zig"); +const result_mod = @import("result.zig"); pub const CellValue = types.CellValue; pub const RowState = types.RowState; @@ -15,6 +17,9 @@ pub const Row = types.Row; pub const TableSchema = schema_mod.TableSchema; pub const MAX_EDIT_BUFFER = types.MAX_EDIT_BUFFER; +// Re-export AdvancedTableResult desde result.zig +pub const AdvancedTableResult = result_mod.AdvancedTableResult; + // ============================================================================= // AdvancedTable State // ============================================================================= @@ -906,148 +911,31 @@ pub const AdvancedTableState = struct { } // ========================================================================= - // Internal Helpers + // Internal Helpers (delegados a state_helpers.zig) // ========================================================================= - const MAX_STATE_ENTRIES = 64; // Maximum entries we expect in state maps - /// Shift row indices down (after insert) fn shiftRowIndicesDown(self: *AdvancedTableState, insert_index: usize) void { - shiftMapIndicesDown(&self.dirty_rows, insert_index); - shiftMapIndicesDown(&self.new_rows, insert_index); - shiftMapIndicesDown(&self.deleted_rows, insert_index); - shiftMapIndicesDown(&self.validation_errors, insert_index); + state_helpers.shiftMapIndicesDown(&self.dirty_rows, insert_index); + state_helpers.shiftMapIndicesDown(&self.new_rows, insert_index); + state_helpers.shiftMapIndicesDown(&self.deleted_rows, insert_index); + state_helpers.shiftMapIndicesDown(&self.validation_errors, insert_index); } /// Shift row indices up (after delete) fn shiftRowIndicesUp(self: *AdvancedTableState, delete_index: usize) void { - shiftMapIndicesUp(&self.dirty_rows, delete_index); - shiftMapIndicesUp(&self.new_rows, delete_index); - shiftMapIndicesUp(&self.deleted_rows, delete_index); - shiftMapIndicesUp(&self.validation_errors, delete_index); - } -}; - -// ============================================================================= -// Map Shifting Helpers (standalone functions to avoid allocator issues) -// ============================================================================= - -const Entry = struct { key: usize, value: bool }; - -fn shiftMapIndicesDown(map: *std.AutoHashMap(usize, bool), insert_index: usize) void { - // Use bounded array to avoid allocation - var entries: [AdvancedTableState.MAX_STATE_ENTRIES]Entry = undefined; - var count: usize = 0; - - // Collect entries that need shifting - var iter = map.iterator(); - while (iter.next()) |entry| { - if (count >= AdvancedTableState.MAX_STATE_ENTRIES) break; - if (entry.key_ptr.* >= insert_index) { - entries[count] = .{ .key = entry.key_ptr.* + 1, .value = entry.value_ptr.* }; - } else { - entries[count] = .{ .key = entry.key_ptr.*, .value = entry.value_ptr.* }; - } - count += 1; + state_helpers.shiftMapIndicesUp(&self.dirty_rows, delete_index); + state_helpers.shiftMapIndicesUp(&self.new_rows, delete_index); + state_helpers.shiftMapIndicesUp(&self.deleted_rows, delete_index); + state_helpers.shiftMapIndicesUp(&self.validation_errors, delete_index); } - // Rebuild map - map.clearRetainingCapacity(); - for (entries[0..count]) |e| { - map.put(e.key, e.value) catch {}; - } -} - -fn shiftMapIndicesUp(map: *std.AutoHashMap(usize, bool), delete_index: usize) void { - // Use bounded array to avoid allocation - var entries: [AdvancedTableState.MAX_STATE_ENTRIES]Entry = undefined; - var count: usize = 0; - - // Collect entries, skipping deleted and shifting down - var iter = map.iterator(); - while (iter.next()) |entry| { - if (count >= AdvancedTableState.MAX_STATE_ENTRIES) break; - if (entry.key_ptr.* == delete_index) { - continue; // Skip deleted index - } else if (entry.key_ptr.* > delete_index) { - entries[count] = .{ .key = entry.key_ptr.* - 1, .value = entry.value_ptr.* }; - } else { - entries[count] = .{ .key = entry.key_ptr.*, .value = entry.value_ptr.* }; - } - count += 1; - } - - // Rebuild map - map.clearRetainingCapacity(); - for (entries[0..count]) |e| { - map.put(e.key, e.value) catch {}; - } -} - -// ============================================================================= -// Result Type -// ============================================================================= - -/// Result returned from advancedTable() call -pub const AdvancedTableResult = struct { - // Selection - selection_changed: bool = false, - selected_row: ?usize = null, - selected_col: ?usize = null, - - // Editing - edit_started: bool = false, - edit_ended: bool = false, - cell_edited: bool = false, - - // Sorting - sort_changed: bool = false, - sort_column: ?usize = null, - sort_direction: SortDirection = .none, - - // Row operations - row_inserted: bool = false, - row_deleted: bool = false, - row_moved: bool = false, - - // Auto-CRUD - crud_action: ?CRUDAction = null, - crud_success: bool = true, - - // Lookup (Phase 7) - lookup_success: ?bool = null, // null = no lookup, true = found, false = not found - - // Focus - clicked: bool = false, - - // ========================================================================= - // Edición CRUD Excel-style (simétrico con VirtualAdvancedTableResult) - // ========================================================================= - - /// Una fila fue completada (el usuario cambió de fila, tenía cambios pendientes) - row_committed: bool = false, - - /// ID de la fila que se hizo commit (índice en AdvancedTable) - row_commit_id: i64 = table_core.NEW_ROW_ID, - - /// Es un INSERT (ghost row) o UPDATE (fila existente) - row_commit_is_insert: bool = false, - - /// Cambios de la fila (válidos si row_committed = true) - row_changes: [table_core.MAX_PENDING_COLUMNS]table_core.PendingCellChange = undefined, - - /// Número de cambios en row_changes - row_changes_count: usize = 0, - - /// Tab presionado para salir del widget - tab_out: bool = false, - - /// Shift estaba presionado con Tab - tab_shift: bool = false, - - /// Obtiene los cambios como slice - pub fn getRowChanges(self: *const AdvancedTableResult) []const table_core.PendingCellChange { - return self.row_changes[0..self.row_changes_count]; + /// Swap state map entries between two row indices + fn swapRowStates(self: *AdvancedTableState, idx_a: usize, idx_b: usize) void { + state_helpers.swapMapEntries(&self.dirty_rows, idx_a, idx_b); + state_helpers.swapMapEntries(&self.new_rows, idx_a, idx_b); + state_helpers.swapMapEntries(&self.deleted_rows, idx_a, idx_b); + state_helpers.swapMapEntries(&self.validation_errors, idx_a, idx_b); } }; diff --git a/src/widgets/advanced_table/state_helpers.zig b/src/widgets/advanced_table/state_helpers.zig new file mode 100644 index 0000000..f4239f7 --- /dev/null +++ b/src/widgets/advanced_table/state_helpers.zig @@ -0,0 +1,141 @@ +//! AdvancedTable State Helpers - Map shifting utilities +//! +//! Funciones auxiliares para manipular los state maps (dirty_rows, new_rows, etc.) +//! cuando se insertan o eliminan filas. +//! +//! Extraído de state.zig para mejorar modularidad. + +const std = @import("std"); + +/// Maximum entries we expect in state maps +pub const MAX_STATE_ENTRIES = 64; + +/// Entry for temporary storage during map rebuild +const Entry = struct { key: usize, value: bool }; + +/// Shift row indices down (after insert) +/// All indices >= insert_index are incremented by 1 +pub fn shiftMapIndicesDown(map: *std.AutoHashMap(usize, bool), insert_index: usize) void { + // Use bounded array to avoid allocation + var entries: [MAX_STATE_ENTRIES]Entry = undefined; + var count: usize = 0; + + // Collect entries that need shifting + var iter = map.iterator(); + while (iter.next()) |entry| { + if (count >= MAX_STATE_ENTRIES) break; + if (entry.key_ptr.* >= insert_index) { + entries[count] = .{ .key = entry.key_ptr.* + 1, .value = entry.value_ptr.* }; + } else { + entries[count] = .{ .key = entry.key_ptr.*, .value = entry.value_ptr.* }; + } + count += 1; + } + + // Rebuild map + map.clearRetainingCapacity(); + for (entries[0..count]) |e| { + map.put(e.key, e.value) catch {}; + } +} + +/// Shift row indices up (after delete) +/// All indices > delete_index are decremented by 1 +/// The entry at delete_index is removed +pub fn shiftMapIndicesUp(map: *std.AutoHashMap(usize, bool), delete_index: usize) void { + // Use bounded array to avoid allocation + var entries: [MAX_STATE_ENTRIES]Entry = undefined; + var count: usize = 0; + + // Collect entries, skipping deleted and shifting down + var iter = map.iterator(); + while (iter.next()) |entry| { + if (count >= MAX_STATE_ENTRIES) break; + if (entry.key_ptr.* == delete_index) { + continue; // Skip deleted index + } else if (entry.key_ptr.* > delete_index) { + entries[count] = .{ .key = entry.key_ptr.* - 1, .value = entry.value_ptr.* }; + } else { + entries[count] = .{ .key = entry.key_ptr.*, .value = entry.value_ptr.* }; + } + count += 1; + } + + // Rebuild map + map.clearRetainingCapacity(); + for (entries[0..count]) |e| { + map.put(e.key, e.value) catch {}; + } +} + +/// Swap entries between two indices in a map +/// Used when swapping rows (move up/down) +pub fn swapMapEntries(map: *std.AutoHashMap(usize, bool), idx_a: usize, idx_b: usize) void { + const val_a = map.get(idx_a); + const val_b = map.get(idx_b); + + if (val_a != null and val_b != null) { + // Both exist - no change needed (both stay) + } else if (val_a) |v| { + // Only a exists - move to b + _ = map.remove(idx_a); + map.put(idx_b, v) catch {}; + } else if (val_b) |v| { + // Only b exists - move to a + _ = map.remove(idx_b); + map.put(idx_a, v) catch {}; + } + // Neither exists - nothing to do +} + +// ============================================================================= +// Tests +// ============================================================================= + +test "shiftMapIndicesDown" { + var map = std.AutoHashMap(usize, bool).init(std.testing.allocator); + defer map.deinit(); + + try map.put(0, true); + try map.put(2, true); + try map.put(5, true); + + // Insert at index 2 - indices 2 and 5 should shift to 3 and 6 + shiftMapIndicesDown(&map, 2); + + try std.testing.expect(map.get(0) != null); + try std.testing.expect(map.get(2) == null); // Shifted to 3 + try std.testing.expect(map.get(3) != null); + try std.testing.expect(map.get(5) == null); // Shifted to 6 + try std.testing.expect(map.get(6) != null); +} + +test "shiftMapIndicesUp" { + var map = std.AutoHashMap(usize, bool).init(std.testing.allocator); + defer map.deinit(); + + try map.put(0, true); + try map.put(2, true); + try map.put(5, true); + + // Delete at index 2 - index 5 should shift to 4, index 2 is removed + shiftMapIndicesUp(&map, 2); + + try std.testing.expect(map.get(0) != null); + try std.testing.expect(map.get(2) == null); // Deleted + try std.testing.expect(map.get(4) != null); // Shifted from 5 + try std.testing.expect(map.get(5) == null); +} + +test "swapMapEntries" { + var map = std.AutoHashMap(usize, bool).init(std.testing.allocator); + defer map.deinit(); + + try map.put(1, true); + + // Swap 1 and 3 (only 1 exists) + swapMapEntries(&map, 1, 3); + + try std.testing.expect(map.get(1) == null); + try std.testing.expect(map.get(3) != null); +}