\brief Handle regular character keys
| 439 | |
| 440 | /// \brief Handle regular character keys |
| 441 | CLIWide::KeyAction CLIWide::handle_regular_key(const KEY_EVENT_RECORD& keyEvent, InputState& state) { |
| 442 | WORD vkCode = keyEvent.wVirtualKeyCode; |
| 443 | WCHAR unicodeChar = keyEvent.uChar.UnicodeChar; |
| 444 | DWORD controlKeyState = keyEvent.dwControlKeyState; |
| 445 | |
| 446 | // Handle TAB key |
| 447 | if (vkCode == VK_TAB) { |
| 448 | // Check if adding tab would cause wrapping |
| 449 | std::string test_line = state.lines[state.current_line_index]; |
| 450 | test_line.insert(state.cursor_pos, "\t"); |
| 451 | |
| 452 | if (needs_line_wrap(test_line, state.prompt_len)) { |
| 453 | // Line would wrap, so create new line first, then insert tab |
| 454 | create_new_line_and_jump(state); |
| 455 | // Now insert the tab in the new line |
| 456 | state.lines[state.current_line_index].insert(state.cursor_pos, "\t"); |
| 457 | state.cursor_pos += 1; |
| 458 | |
| 459 | // Redraw the new line with the tab |
| 460 | int visual_cursor_col = state.prompt_len + get_visual_cursor_position(state.lines[state.current_line_index], state.cursor_pos); |
| 461 | move_cursor_to_column(state.prompt_len); |
| 462 | clear_line_from_cursor(); |
| 463 | std::cout << visualize_tabs(state.lines[state.current_line_index]); |
| 464 | set_cursor_position(visual_cursor_col); |
| 465 | } else { |
| 466 | // No wrapping needed, insert tab normally |
| 467 | size_t insertion_pos = state.cursor_pos; |
| 468 | state.lines[state.current_line_index].insert(state.cursor_pos, "\t"); |
| 469 | state.cursor_pos += 1; |
| 470 | |
| 471 | // Redraw line from the insertion point with tab visualization |
| 472 | int visual_cursor_col = state.prompt_len + get_visual_cursor_position(state.lines[state.current_line_index], state.cursor_pos); |
| 473 | move_cursor_to_column(state.prompt_len + get_visual_cursor_position(state.lines[state.current_line_index], insertion_pos)); |
| 474 | clear_line_from_cursor(); |
| 475 | std::cout << visualize_tabs(state.lines[state.current_line_index].substr(insertion_pos)); |
| 476 | set_cursor_position(visual_cursor_col); |
| 477 | } |
| 478 | |
| 479 | state.history_nav_index = -1; |
| 480 | return KeyAction::CONTINUE; |
| 481 | } |
| 482 | |
| 483 | // Handle Enter key (including Shift + Enter detection) |
| 484 | if (vkCode == VK_RETURN) { |
| 485 | bool shift_pressed = (GetKeyState(VK_SHIFT) & 0x8000) != 0; |
| 486 | std::cout << std::endl; |
| 487 | |
| 488 | if (shift_pressed) { |
| 489 | // Shift + Enter: Create new line directly |
| 490 | state.lines.push_back(""); |
| 491 | state.is_wrapped_line.push_back(false); // User-created line, not wrapped |
| 492 | state.current_line_index++; |
| 493 | state.cursor_pos = 0; |
| 494 | state.prompt_len = 4; // "... " for continuation lines |
| 495 | |
| 496 | // Safety check: ensure vectors stay in sync |
| 497 | if (state.lines.size() != state.is_wrapped_line.size()) { |
| 498 | size_t min_size = (state.lines.size() < state.is_wrapped_line.size()) ? state.lines.size() : state.is_wrapped_line.size(); |