diff --git a/tui/input.py b/tui/input.py index 0fe384b..f4aa532 100644 --- a/tui/input.py +++ b/tui/input.py @@ -7,20 +7,65 @@ import os from .agent import submit, log +def _build_visual(buffer, max_chars): + # Build list of (logical_line_idx, start_col) for each visual line. + visual = [] + for i, line in enumerate(buffer): + if not line: + visual.append((i, 0)) + else: + for start in range(0, max(len(line), 1), max_chars): + visual.append((i, start)) + return visual + + +def _find_visual(visual, logical_line, col): + # Find visual line index for given logical line and column. + best = 0 + for idx, (li, start) in enumerate(visual): + if li == logical_line: + best = idx + if start <= col: + break + return best + + def handle_key(app, stdscr, key): + max_chars = app.w - 6 # usable width in input box + visual = _build_visual(app.input_buffer, max_chars) + cur_visual = _find_visual(visual, app.input_line, app.input_col) + + processing = app.processing + + # -- Always allowed (even during processing) -- + if key == 3: # Ctrl+C → exit + app.running = False + elif key == curses.KEY_PPAGE: + app.scroll = max(0, app.scroll - (app.h - 10) // 2) + elif key == curses.KEY_NPAGE: + app.scroll += (app.h - 10) // 2 + elif key == curses.KEY_RESIZE: + pass + + # -- Blocked during processing -- + elif processing: + pass # ignore all other keys while processing + # -- Ctrl shortcuts -- - if key == 4: # Ctrl+D → submit query ke LLM + elif key == 4: # Ctrl+D → submit query ke LLM submit(app, stdscr) elif key == 23: # Ctrl+W → popup ganti workspace workspace_popup(app, stdscr) - elif key == 3: # Ctrl+C → exit - app.running = False elif key == 12: # Ctrl+L → clear chat log app.log.clear() - # -- Enter: buat baris baru di input buffer -- + # -- Enter: split logical line at cursor position -- elif key in (curses.KEY_ENTER, 10, 13): - app.input_buffer.insert(app.input_line + 1, "") + line = app.input_buffer[app.input_line] + left = line[:app.input_col] + right = line[app.input_col:] + app.input_buffer[app.input_line] = left + app.input_buffer.insert(app.input_line + 1, right) app.input_line += 1 app.input_col = 0 @@ -40,17 +85,15 @@ def handle_key(app, stdscr, key): # -- Navigation arrows -- elif key == curses.KEY_UP: - if app.input_line > 0: - app.input_line -= 1 - app.input_col = min( - app.input_col, len(app.input_buffer[app.input_line]) - ) + if cur_visual > 0: + prev_li, prev_start = visual[cur_visual - 1] + app.input_line = prev_li + app.input_col = min(app.input_col, len(app.input_buffer[prev_li])) elif key == curses.KEY_DOWN: - if app.input_line < len(app.input_buffer) - 1: - app.input_line += 1 - app.input_col = min( - app.input_col, len(app.input_buffer[app.input_line]) - ) + if cur_visual < len(visual) - 1: + next_li, next_start = visual[cur_visual + 1] + app.input_line = next_li + app.input_col = min(app.input_col, len(app.input_buffer[next_li])) elif key == curses.KEY_LEFT: if app.input_col > 0: app.input_col -= 1 @@ -68,16 +111,6 @@ def handle_key(app, stdscr, key): elif key == curses.KEY_END: app.input_col = len(app.input_buffer[app.input_line]) - # -- Page Up / Down: scroll chat area -- - elif key == curses.KEY_PPAGE: - app.scroll = max(0, app.scroll - (app.h - 10)) - elif key == curses.KEY_NPAGE: - app.scroll += app.h - 10 - - # -- Resize terminal: tidak perlu action, next loop akan baca ukuran baru -- - elif key == curses.KEY_RESIZE: - pass - # -- Tab: insert 4 spasi -- elif key == 9: line = app.input_buffer[app.input_line]