@@ -47,6 +47,7 @@ module readline |
| 47 | 47 | integer, parameter :: KEY_TAB = 9 |
| 48 | 48 | integer, parameter :: KEY_CTRL_C = 3 |
| 49 | 49 | integer, parameter :: KEY_CTRL_D = 4 |
| 50 | + integer, parameter :: KEY_CTRL_V = 22 ! Paste from system clipboard / kill buffer |
| 50 | 51 | integer, parameter :: KEY_CTRL_X = 24 ! Process kill mode |
| 51 | 52 | integer, parameter :: KEY_CTRL_A = 1 ! Home (beginning of line) |
| 52 | 53 | integer, parameter :: KEY_CTRL_E = 5 ! End (end of line) |
@@ -1673,6 +1674,13 @@ contains |
| 1673 | 1674 | call handle_kill_word(module_input_state) |
| 1674 | 1675 | end if |
| 1675 | 1676 | |
| 1677 | + case(KEY_CTRL_V) |
| 1678 | + ! Ctrl+V — paste (Sprint 5). Reads from the system clipboard |
| 1679 | + ! first; falls back to the in-session kill_buffer if no |
| 1680 | + ! clipboard tool is available or the clipboard is empty. |
| 1681 | + ! If a selection is active, it's deleted first (paste-over). |
| 1682 | + if (.not. module_input_state%in_search) call handle_paste(module_input_state) |
| 1683 | + |
| 1676 | 1684 | case(KEY_CTRL_Y) |
| 1677 | 1685 | ! Yank - no-op in search mode |
| 1678 | 1686 | if (.not. module_input_state%in_search) call handle_yank(module_input_state) |
@@ -7514,7 +7522,49 @@ contains |
| 7514 | 7522 | input_state%cursor_pos = input_state%cursor_pos + insert_len |
| 7515 | 7523 | input_state%dirty = .true. |
| 7516 | 7524 | end subroutine |
| 7517 | | - |
| 7525 | + |
| 7526 | + ! Ctrl+V paste handler (Sprint 5). Reads from the system clipboard; |
| 7527 | + ! if the clipboard is empty or no tool is available, falls back to |
| 7528 | + ! yanking from the in-session kill_buffer. If a selection is active |
| 7529 | + ! it is deleted first (paste-over behavior, same as Ctrl+Y). |
| 7530 | + subroutine handle_paste(input_state) |
| 7531 | + type(input_state_t), intent(inout) :: input_state |
| 7532 | + character(len=MAX_LINE_LEN) :: paste_buf |
| 7533 | + integer :: paste_len, insert_len, i, j |
| 7534 | + |
| 7535 | + ! Delete active selection first (paste-over). |
| 7536 | + if (input_state%selection_active) call delete_selection(input_state) |
| 7537 | + |
| 7538 | + ! Try the system clipboard. |
| 7539 | + paste_len = 0 |
| 7540 | + call clipboard_paste(paste_buf, MAX_LINE_LEN, paste_len) |
| 7541 | + |
| 7542 | + if (paste_len > 0) then |
| 7543 | + ! Truncate to available space. |
| 7544 | + insert_len = min(paste_len, MAX_LINE_LEN - input_state%length) |
| 7545 | + if (insert_len <= 0) return |
| 7546 | + |
| 7547 | + ! Shift existing text right. |
| 7548 | + do i = input_state%length, input_state%cursor_pos + 1, -1 |
| 7549 | + if (i + insert_len <= MAX_LINE_LEN) then |
| 7550 | + call state_buffer_set_char(input_state, i + insert_len, state_buffer_get_char(input_state, i)) |
| 7551 | + end if |
| 7552 | + end do |
| 7553 | + |
| 7554 | + ! Insert clipboard text at cursor. |
| 7555 | + do j = 1, insert_len |
| 7556 | + call state_buffer_set_char(input_state, input_state%cursor_pos + j, paste_buf(j:j)) |
| 7557 | + end do |
| 7558 | + |
| 7559 | + input_state%length = input_state%length + insert_len |
| 7560 | + input_state%cursor_pos = input_state%cursor_pos + insert_len |
| 7561 | + input_state%dirty = .true. |
| 7562 | + else |
| 7563 | + ! Clipboard empty or unavailable — fall back to kill buffer (same as C-y). |
| 7564 | + call handle_yank(input_state) |
| 7565 | + end if |
| 7566 | + end subroutine handle_paste |
| 7567 | + |
| 7518 | 7568 | subroutine handle_clear_screen(input_state, prompt) |
| 7519 | 7569 | type(input_state_t), intent(inout) :: input_state |
| 7520 | 7570 | character(len=*), intent(in) :: prompt |