From c9bdf56a80bef652757bd137f97cf8c5eb83d594 Mon Sep 17 00:00:00 2001 From: reugenio Date: Sun, 28 Dec 2025 22:01:17 +0100 Subject: [PATCH] =?UTF-8?q?refactor:=20Remove=20injection=20logic,=20prepa?= =?UTF-8?q?re=20for=20Modo=20Inserci=C3=B3n=20Cronol=C3=B3gico?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove injected_row_idx, injected_committed from VirtualAdvancedTableState - Remove is_injected, injection_index from RowEditBuffer and RowCommitInfo - Remove startInjectedEdit() method - Simplify PagedDataSource (no injection offset) - Simplify drawRowsWithDataSource (no injection handling) - Add is_insertion_mode, insertion_session_active to State - Add enterInsertionMode(), exitInsertionMode(), isInInsertionMode() methods - Ctrl+N now emits insert_row_requested for Panel to handle Part of Modo Inserción Cronológico implementation. --- src/widgets/advanced_table/advanced_table.zig | 4 +- src/widgets/table_core.zig | 42 +----- .../paged_datasource.zig | 55 +------ src/widgets/virtual_advanced_table/state.zig | 57 +++----- .../virtual_advanced_table.zig | 136 ++---------------- 5 files changed, 41 insertions(+), 253 deletions(-) diff --git a/src/widgets/advanced_table/advanced_table.zig b/src/widgets/advanced_table/advanced_table.zig index 22c08d4..0a7c8f1 100644 --- a/src/widgets/advanced_table/advanced_table.zig +++ b/src/widgets/advanced_table/advanced_table.zig @@ -807,8 +807,8 @@ fn handleKeyboard( 0; if (table_state.insertRow(insert_idx)) |new_idx| { table_state.selectCell(new_idx, 0); - // Inicializar buffer para fila inyectada (Excel-style) - table_state.row_edit_buffer.startInjectedEdit(new_idx); + // Inicializar buffer para nueva fila (Excel-style) + table_state.row_edit_buffer.startEdit(table_core.NEW_ROW_ID, new_idx, true); result.row_inserted = true; result.selection_changed = true; } else |_| {} diff --git a/src/widgets/table_core.zig b/src/widgets/table_core.zig index 6c6d5a4..fb4f424 100644 --- a/src/widgets/table_core.zig +++ b/src/widgets/table_core.zig @@ -678,10 +678,12 @@ pub fn drawRowsWithDataSource( } // Obtener texto de la celda - // PRIORIDAD 1: Valor pendiente en RowEditBuffer (lo que el usuario tecleó) - // PRIORIDAD 2: Valor del DataSource (BD o memoria) + // PRIORIDAD 1: Fila con cambios pendientes → leer del buffer + // PRIORIDAD 2: Leer del DataSource (BD o memoria) var cell_text: []const u8 = ""; const row_id = datasource.getRowId(row_idx); + + // Intentar leer del buffer si tiene cambios pendientes if (config.edit_buffer) |eb| { if (eb.row_id == row_id) { if (eb.getPendingValue(col_idx)) |pending| { @@ -689,6 +691,8 @@ pub fn drawRowsWithDataSource( } } } + + // Ir al datasource si no tenemos texto del buffer if (cell_text.len == 0) { cell_text = datasource.getCellValueInto(row_idx, col_idx, cell_buffer); } @@ -1309,12 +1313,6 @@ pub const RowEditBuffer = struct { /// Hay cambios pendientes has_changes: bool = false, - /// True si es una fila inyectada (Ctrl+N entre líneas) - is_injected: bool = false, - - /// Índice donde se insertó la fila inyectada (null si no es inyección) - injection_index: ?usize = null, - /// Buffers de valores por columna (almacenamiento fijo) value_buffers: [MAX_PENDING_COLUMNS][MAX_CELL_VALUE_LEN]u8 = undefined, @@ -1333,24 +1331,6 @@ pub const RowEditBuffer = struct { self.row_index = row_index; self.is_new_row = is_new; self.has_changes = false; - self.is_injected = false; - self.injection_index = null; - self.change_count = 0; - for (0..MAX_PENDING_COLUMNS) |i| { - self.changed_cols[i] = false; - self.value_lens[i] = 0; - } - } - - /// Inicializa buffer para una fila inyectada (Ctrl+N entre líneas) - /// insertion_idx es el índice visual donde aparece la fila nueva - pub fn startInjectedEdit(self: *RowEditBuffer, insertion_idx: usize) void { - self.row_id = NEW_ROW_ID; - self.row_index = insertion_idx; - self.is_new_row = true; - self.has_changes = false; - self.is_injected = true; - self.injection_index = insertion_idx; self.change_count = 0; for (0..MAX_PENDING_COLUMNS) |i| { self.changed_cols[i] = false; @@ -1389,8 +1369,6 @@ pub const RowEditBuffer = struct { self.row_index = 0; self.is_new_row = false; self.has_changes = false; - self.is_injected = false; - self.injection_index = null; self.change_count = 0; for (0..MAX_PENDING_COLUMNS) |i| { self.changed_cols[i] = false; @@ -1413,12 +1391,6 @@ pub const RowCommitInfo = struct { /// Número de cambios change_count: usize, - - /// True si era una fila inyectada (Ctrl+N entre líneas) - is_injected: bool = false, - - /// Índice visual donde fue inyectada (válido si is_injected = true) - injection_index: ?usize = null, }; /// Construye la info de commit desde un RowEditBuffer @@ -1445,8 +1417,6 @@ pub fn buildCommitInfo( .is_insert = buffer.is_new_row, .changes = changes_out[0..count], .change_count = count, - .is_injected = buffer.is_injected, - .injection_index = buffer.injection_index, }; } diff --git a/src/widgets/virtual_advanced_table/paged_datasource.zig b/src/widgets/virtual_advanced_table/paged_datasource.zig index 8e1e64a..ee7cfdb 100644 --- a/src/widgets/virtual_advanced_table/paged_datasource.zig +++ b/src/widgets/virtual_advanced_table/paged_datasource.zig @@ -51,51 +51,16 @@ pub const PagedDataSource = struct { // Implementación de TableDataSource // ========================================================================= - /// Retorna el número total de filas (filtered count + inyección) + /// Retorna el número total de filas (filtered count + inyección visual) + /// Retorna el número de filas disponibles pub fn getRowCount(self: *Self) usize { - // Usar conteo filtrado si está disponible const count_info = self.state.getDisplayCount(); - var count = count_info.value; - - // Si hay una fila inyectada, sumar 1 al conteo visual - if (self.state.injected_row_idx != null) { - count += 1; - } - - return count; + return count_info.value; } /// Escribe el valor de una celda en el buffer proporcionado. - /// El row es índice global, se convierte a índice de ventana. - /// Maneja filas inyectadas (Ctrl+N entre líneas). - /// Retorna slice del buffer con el contenido. pub fn getCellValueInto(self: *Self, row: usize, col: usize, buf: []u8) []const u8 { - // Validar columna if (col >= self.columns.len) return ""; - - // ===================================================================== - // Manejo de fila inyectada - // ===================================================================== - if (self.state.injected_row_idx) |inj_idx| { - if (row == inj_idx) { - // Esta es la fila inyectada - leer del edit buffer - if (self.state.row_edit_buffer.getPendingValue(col)) |pending| { - std.debug.print("[PDS-DEBUG] injected row={} col={} pending=\"{s}\"\n", .{ row, col, pending }); - const copy_len = @min(pending.len, buf.len); - @memcpy(buf[0..copy_len], pending[0..copy_len]); - return buf[0..copy_len]; - } - // Sin valor pendiente - retornar vacío (celda nueva) - std.debug.print("[PDS-DEBUG] injected row={} col={} NO pending\n", .{ row, col }); - return ""; - } else if (row > inj_idx) { - // Fila después de la inyección - ajustar índice (-1) - return self.getCellValueFromProvider(row - 1, col, buf); - } - // row < inj_idx: continuar normal - } - - // Flujo normal (sin inyección o row < inj_idx) return self.getCellValueFromProvider(row, col, buf); } @@ -122,21 +87,7 @@ pub const PagedDataSource = struct { } /// Retorna el ID único de una fila. - /// Si está en ventana, usa window data. Si no, consulta al provider. - /// Maneja filas inyectadas (Ctrl+N entre líneas). pub fn getRowId(self: *Self, row: usize) i64 { - // Manejo de fila inyectada - if (self.state.injected_row_idx) |inj_idx| { - if (row == inj_idx) { - // Esta es la fila inyectada - return self.state.injected_row_id; - } else if (row > inj_idx) { - // Fila después de la inyección - ajustar índice - return self.getRowIdFromProvider(row - 1); - } - } - - // Flujo normal return self.getRowIdFromProvider(row); } diff --git a/src/widgets/virtual_advanced_table/state.zig b/src/widgets/virtual_advanced_table/state.zig index 2e81ac7..bf08756 100644 --- a/src/widgets/virtual_advanced_table/state.zig +++ b/src/widgets/virtual_advanced_table/state.zig @@ -164,17 +164,15 @@ pub const VirtualAdvancedTableState = struct { row_edit_buffer: table_core.RowEditBuffer = .{}, // ========================================================================= - // Estado de inyección local (Ctrl+N entre líneas) + // Modo Inserción Cronológico (reemplaza inyección local) // ========================================================================= - /// Índice visual donde está la fila inyectada (null = sin inyección) - injected_row_idx: ?usize = null, + /// True si estamos en modo inserción (Ctrl+N activo) + is_insertion_mode: bool = false, - /// ID de la fila inyectada tras commit (NEW_ROW_ID si aún no guardada) - injected_row_id: i64 = table_core.NEW_ROW_ID, - - /// True si la fila inyectada ya fue guardada en BD (tiene ID real) - injected_committed: bool = false, + /// IDs de filas insertadas en esta sesión (para ORDER BY cronológico) + /// El Panel gestiona esta lista via el DataProvider + insertion_session_active: bool = false, const Self = @This(); @@ -730,43 +728,24 @@ pub const VirtualAdvancedTableState = struct { } // ========================================================================= - // Métodos de inyección local (Ctrl+N entre líneas) + // Métodos de Modo Inserción Cronológico // ========================================================================= - /// Verifica si hay una fila inyectada activa - pub fn hasInjection(self: *const Self) bool { - return self.injected_row_idx != null; + /// Entra en modo inserción + pub fn enterInsertionMode(self: *Self) void { + self.is_insertion_mode = true; + self.insertion_session_active = true; } - /// Inicia una inyección en el índice especificado - pub fn startInjection(self: *Self, visual_idx: usize) void { - self.injected_row_idx = visual_idx; - self.injected_row_id = table_core.NEW_ROW_ID; - self.injected_committed = false; - self.row_edit_buffer.startInjectedEdit(visual_idx); + /// Sale del modo inserción + pub fn exitInsertionMode(self: *Self) void { + self.is_insertion_mode = false; + self.insertion_session_active = false; } - /// Marca la fila inyectada como guardada en BD - pub fn markInjectionCommitted(self: *Self, real_id: i64) void { - self.injected_row_id = real_id; - self.injected_committed = true; - } - - /// Limpia el estado de inyección (después de reload) - pub fn clearInjection(self: *Self) void { - self.injected_row_idx = null; - self.injected_row_id = table_core.NEW_ROW_ID; - self.injected_committed = false; - } - - /// Verifica si la fila inyectada está visible en la ventana actual - pub fn isInjectionVisible(self: *const Self, visible_rows: usize) bool { - if (self.injected_row_idx) |idx| { - const scroll_start = self.nav.scroll_row; - const scroll_end = scroll_start + visible_rows; - return idx >= scroll_start and idx < scroll_end; - } - return false; + /// Verifica si estamos en modo inserción + pub fn isInInsertionMode(self: *const Self) bool { + return self.is_insertion_mode; } }; diff --git a/src/widgets/virtual_advanced_table/virtual_advanced_table.zig b/src/widgets/virtual_advanced_table/virtual_advanced_table.zig index 86e2330..172577e 100644 --- a/src/widgets/virtual_advanced_table/virtual_advanced_table.zig +++ b/src/widgets/virtual_advanced_table/virtual_advanced_table.zig @@ -130,19 +130,6 @@ pub const VirtualAdvancedTableResult = struct { /// Shift estaba presionado con Tab (para tab_out inverso) tab_shift: bool = false, - // ========================================================================= - // Inyección local (Ctrl+N entre líneas) - // ========================================================================= - - /// Una fila inyectada fue guardada en BD (el panel debe hacer INSERT) - injection_committed: bool = false, - - /// Índice visual donde se inyectó la fila - injection_row_idx: ?usize = null, - - /// La tabla necesita recargarse desde BD (fila inyectada salió del viewport) - needs_reload: bool = false, - // ========================================================================= // Compatibilidad (DEPRECADO - usar row_committed) // ========================================================================= @@ -197,11 +184,6 @@ pub fn virtualAdvancedTableRect( ) VirtualAdvancedTableResult { var result = VirtualAdvancedTableResult{}; - // Debug: verificar si hay inyección al inicio del frame - if (list_state.injected_row_idx != null) { - std.debug.print("[VT-FRAME] Frame con inyección activa: idx={}\n", .{list_state.injected_row_idx.?}); - } - if (bounds.isEmpty() or config.columns.len == 0) return result; // Reset frame flags @@ -348,12 +330,11 @@ pub fn virtualAdvancedTableRect( const edited_cell = list_state.getEditingCell().?; const new_value = list_state.getEditText(); - std.debug.print("[VT-DEBUG] editor committed: cell=({},{}) value=\"{s}\" hasChanged={} is_injected={}\n", .{ + std.debug.print("[VT-DEBUG] editor committed: cell=({},{}) value=\"{s}\" hasChanged={}\n", .{ edited_cell.row, edited_cell.col, new_value, list_state.hasValueChanged(), - list_state.row_edit_buffer.is_injected, }); // Añadir cambio al buffer de fila (NO commit inmediato) @@ -449,53 +430,28 @@ pub fn virtualAdvancedTableRect( const is_tab = result.navigate_direction == .next_cell or result.navigate_direction == .prev_cell; if (is_tab) { // Wrapper para DataProvider que implementa getRowId(usize) -> i64 - // Tiene en cuenta filas inyectadas (Ctrl+N) y ghost row const RowIdGetter = struct { prov: DataProvider, total: usize, - injected_idx: ?usize, pub fn getRowId(self: @This(), row: usize) i64 { - // Fila inyectada siempre retorna NEW_ROW_ID - if (self.injected_idx) |inj_idx| { - if (row == inj_idx) { - std.debug.print("[ROW-ID-GETTER] row={} == inj_idx={} -> NEW_ROW_ID\n", .{ row, inj_idx }); - return table_core.NEW_ROW_ID; - } - // Filas después de inyección: ajustar índice hacia provider - if (row > inj_idx) { - const adjusted_row = row - 1; - if (adjusted_row >= self.total) return table_core.NEW_ROW_ID; - const id = self.prov.getRowId(adjusted_row) orelse table_core.NEW_ROW_ID; - std.debug.print("[ROW-ID-GETTER] row={} > inj_idx={}, adjusted={} -> id={}\n", .{ row, inj_idx, adjusted_row, id }); - return id; - } - } // Ghost row está al final if (row >= self.total) { - std.debug.print("[ROW-ID-GETTER] row={} >= total={} -> NEW_ROW_ID (ghost)\n", .{ row, self.total }); return table_core.NEW_ROW_ID; } - const id = self.prov.getRowId(row) orelse table_core.NEW_ROW_ID; - std.debug.print("[ROW-ID-GETTER] row={} normal -> id={}\n", .{ row, id }); - return id; + return self.prov.getRowId(row) orelse table_core.NEW_ROW_ID; } }; const getter = RowIdGetter{ .prov = provider, .total = total_rows, - .injected_idx = list_state.injected_row_idx, }; const current_row = list_state.getSelectedRow() orelse 0; const forward = result.navigate_direction == .next_cell; const num_cols = config.columns.len; - // VirtualAdvancedTable siempre tiene ghost row disponible - // Si hay inyección, sumar 1 extra al conteo visual - var num_rows = total_rows + 1; // +1 para ghost row - if (list_state.injected_row_idx != null) { - num_rows += 1; // +1 para fila inyectada - } + // +1 para ghost row + const num_rows = total_rows + 1; const plan = table_core.planTabNavigation( &list_state.row_edit_buffer, @@ -539,12 +495,6 @@ pub fn virtualAdvancedTableRect( result.row_commit_is_insert = info.is_insert; result.row_changes_count = info.change_count; result.row_changed = true; - - // Propagar info de inyección si aplica - if (info.is_injected) { - result.injection_committed = true; - result.injection_row_idx = info.injection_index; - } } } else if (plan.action == .move_with_commit and plan.new_row == current_row) { std.debug.print("[VT-TAB] Commit SUPRIMIDO: misma fila {}, manteniendo buffer\n", .{current_row}); @@ -568,12 +518,6 @@ pub fn virtualAdvancedTableRect( result.row_commit_is_insert = info.is_insert; result.row_changes_count = info.change_count; result.row_changed = true; - - // Propagar info de inyección si aplica - if (info.is_injected) { - result.injection_committed = true; - result.injection_row_idx = info.injection_index; - } } } @@ -593,10 +537,6 @@ pub fn virtualAdvancedTableRect( const new_row_idx = list_state.getSelectedRow() orelse 0; const is_ghost = table_core.isGhostRow(new_row_id); - // Detectar si estamos abandonando una fila inyectada - const was_injected = list_state.row_edit_buffer.is_injected; - const injection_idx = list_state.row_edit_buffer.injection_index; - // checkRowChangeAndCommit compara row_ids y hace commit si son diferentes if (table_core.checkRowChangeAndCommit( &list_state.row_edit_buffer, @@ -609,32 +549,10 @@ pub fn virtualAdvancedTableRect( result.row_commit_id = commit_info.row_id; result.row_commit_is_insert = commit_info.is_insert; result.row_changes_count = commit_info.change_count; - - // Si era una fila inyectada, señalar para que el panel haga INSERT - if (was_injected) { - result.injection_committed = true; - result.injection_row_idx = injection_idx; - // Mantener injected_row_idx en state para renderizado - // (se limpiará cuando el scroll la saque del viewport) - } - - // Compatibilidad result.row_changed = true; } } - // ========================================================================= - // Detectar si la fila inyectada salió del viewport (requiere reload) - // ========================================================================= - if (list_state.hasInjection()) { - // Solo hacer reload si la fila ya fue guardada (injected_committed) - // y ya no es visible en el viewport actual - if (list_state.injected_committed and !list_state.isInjectionVisible(visible_rows)) { - result.needs_reload = true; - list_state.clearInjection(); - } - } - // ========================================================================= // Tips Proactivos (FASE I): Rotar tips cada ~10 segundos // ========================================================================= @@ -1018,18 +936,14 @@ fn drawRows( const table_ds = pds_ptr.toDataSource(); // Convertir selected_id a selected_row (índice global) - // Si hay una fila inyectada activa, esa es la seleccionada - const selected_row: i32 = if (list_state.injected_row_idx) |inj_idx| - @intCast(inj_idx) - else if (list_state.findSelectedInWindow()) |window_idx| + const selected_row: i32 = if (list_state.findSelectedInWindow()) |window_idx| @intCast(list_state.windowToGlobalIndex(window_idx)) else -1; // Calcular rango de filas a dibujar const first_row = list_state.nav.scroll_row; - // Si hay inyección, hay una fila más en la ventana visual - const window_rows = list_state.current_window.len + @as(usize, if (list_state.hasInjection()) 1 else 0); + const window_rows = list_state.current_window.len; const last_row = @min( list_state.nav.scroll_row + visible_rows, list_state.window_start + window_rows, @@ -1302,40 +1216,14 @@ fn handleKeyboard( } // ========================================================================= - // Ctrl+N: Inyección local (insertar fila debajo de la actual) + // Ctrl+N: Modo Inserción Cronológico (INSERT real en BD) // ========================================================================= if (events.insert_row) { - std.debug.print("[VT-CTRL-N] insert_row event, hasInjection={}\n", .{list_state.hasInjection()}); - // Solo permitir si no hay ya una inyección activa - if (!list_state.hasInjection()) { - // Calcular índice de inyección (debajo de la fila actual) - const current_row: usize = if (list_state.findSelectedInWindow()) |window_idx| - list_state.windowToGlobalIndex(window_idx) - else - 0; - - const injection_idx = current_row + 1; - std.debug.print("[VT-CTRL-N] current_row={} injection_idx={}\n", .{ current_row, injection_idx }); - - // Iniciar inyección - list_state.startInjection(injection_idx); - std.debug.print("[VT-CTRL-N] startInjection OK\n", .{}); - - // Mover selección a la fila inyectada - list_state.selected_id = null; // La fila inyectada no tiene ID aún - // La selección visual se maneja por injected_row_idx - - // Señalar que hay una nueva inyección - result.injection_row_idx = injection_idx; - result.selection_changed = true; - - // Iniciar edición automática en primera columna - std.debug.print("[VT-CTRL-N] about to startEditing\n", .{}); - list_state.cell_edit.startEditing(injection_idx, 0, "", null); - list_state.nav.active_col = 0; - std.debug.print("[VT-CTRL-N] DONE\n", .{}); - } - // Nota: insert_row_requested ya NO se emite - la inyección es interna + std.debug.print("[VT-CTRL-N] insert_row event -> emitiendo insert_row_requested\n", .{}); + // Emitir evento para que el Panel haga INSERT real en BD + result.insert_row_requested = true; + // Entrar en modo inserción + list_state.enterInsertionMode(); } // =========================================================================