zcatgui/src/widgets/advanced_table/sorting.zig
reugenio 042ff96141 refactor(advanced_table): Modularizar en drawing, input, helpers, sorting
- Extraer drawing.zig: drawHeader, drawScrollbar, drawEditingOverlay
- Extraer input.zig: handleRowClicks, handleKeyboard, handleEditingKeyboard
- Extraer helpers.zig: commitEdit, parseValue, detectCRUDAction, invokeCallbacks
- Extraer sorting.zig: sortRows, swapRowStates, startsWithIgnoreCase
- Reducir advanced_table.zig de 1443 LOC a ~380 LOC
- Mantener re-exports para compatibilidad con código existente
2025-12-29 10:04:39 +01:00

113 lines
3.9 KiB
Zig

//! AdvancedTable - Ordenación y Búsqueda
//!
//! Funciones de ordenación y búsqueda extraídas del archivo principal.
const std = @import("std");
const types = @import("types.zig");
const state = @import("state.zig");
pub const Row = types.Row;
pub const SortDirection = types.SortDirection;
pub const AdvancedTableState = state.AdvancedTableState;
// =============================================================================
// Sorting
// =============================================================================
/// Sort rows by column value
pub fn sortRows(
table_state: *AdvancedTableState,
column_name: []const u8,
direction: SortDirection,
) void {
if (direction == .none) return;
if (table_state.rows.items.len < 2) return;
const len = table_state.rows.items.len;
var swapped = true;
while (swapped) {
swapped = false;
for (0..len - 1) |i| {
const val_a = table_state.rows.items[i].get(column_name);
const val_b = table_state.rows.items[i + 1].get(column_name);
const cmp = val_a.compare(val_b);
const should_swap = switch (direction) {
.ascending => cmp > 0,
.descending => cmp < 0,
.none => false,
};
if (should_swap) {
std.mem.swap(Row, &table_state.rows.items[i], &table_state.rows.items[i + 1]);
swapRowStates(table_state, i, i + 1);
swapped = true;
}
}
}
}
/// Swap state map entries between two row indices
pub fn swapRowStates(table_state: *AdvancedTableState, idx_a: usize, idx_b: usize) void {
const swapInMap = struct {
fn swap(map: anytype, a: usize, b: usize) void {
const val_a = map.get(a);
const val_b = map.get(b);
if (val_a != null and val_b != null) {
// Both exist - no change needed
} else if (val_a) |v| {
_ = map.remove(a);
map.put(b, v) catch {};
} else if (val_b) |v| {
_ = map.remove(b);
map.put(a, v) catch {};
}
}
}.swap;
swapInMap(&table_state.dirty_rows, idx_a, idx_b);
swapInMap(&table_state.new_rows, idx_a, idx_b);
swapInMap(&table_state.deleted_rows, idx_a, idx_b);
swapInMap(&table_state.validation_errors, idx_a, idx_b);
}
// =============================================================================
// Search Helpers
// =============================================================================
/// Case-insensitive prefix match for incremental search
pub fn startsWithIgnoreCase(haystack: []const u8, needle: []const u8) bool {
if (needle.len > haystack.len) return false;
if (needle.len == 0) return true;
for (needle, 0..) |needle_char, i| {
const haystack_char = haystack[i];
const needle_lower = if (needle_char >= 'A' and needle_char <= 'Z')
needle_char + 32
else
needle_char;
const haystack_lower = if (haystack_char >= 'A' and haystack_char <= 'Z')
haystack_char + 32
else
haystack_char;
if (needle_lower != haystack_lower) return false;
}
return true;
}
// =============================================================================
// Tests
// =============================================================================
test "startsWithIgnoreCase" {
try std.testing.expect(startsWithIgnoreCase("Hello World", "Hello"));
try std.testing.expect(startsWithIgnoreCase("Hello World", "hello"));
try std.testing.expect(startsWithIgnoreCase("hello world", "HELLO"));
try std.testing.expect(startsWithIgnoreCase("anything", ""));
try std.testing.expect(!startsWithIgnoreCase("Hello", "World"));
try std.testing.expect(!startsWithIgnoreCase("Hi", "Hello World"));
}