refactor(advanced_table): Extraer result.zig y state_helpers.zig de state.zig

- 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
This commit is contained in:
reugenio 2025-12-29 10:23:32 +01:00
parent 042ff96141
commit 7d4d4190b8
3 changed files with 234 additions and 132 deletions

View file

@ -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];
}
};

View file

@ -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);
}
};

View file

@ -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);
}