@@ -340,9 +340,11 @@ Bump this only when the Elisp code requires a newer native module
340340(declare-function ghostel--encode-key " ghostel-module" )
341341(declare-function ghostel--focus-event " ghostel-module" )
342342(declare-function ghostel--mode-enabled " ghostel-module" )
343+ (declare-function ghostel--copy-all-text " ghostel-module" )
343344(declare-function ghostel--module-version " ghostel-module" )
344345(declare-function ghostel--mouse-event " ghostel-module" )
345346(declare-function ghostel--new " ghostel-module" )
347+ (declare-function ghostel--redraw-full-scrollback " ghostel-module" )
346348(declare-function ghostel--redraw " ghostel-module" (term &optional full))
347349(declare-function ghostel--scroll " ghostel-module" )
348350(declare-function ghostel--scroll-bottom " ghostel-module" )
@@ -556,6 +558,9 @@ DIR is the module directory."
556558(defvar-local ghostel--copy-mode-active nil
557559 " Non-nil when copy mode is active." )
558560
561+ (defvar-local ghostel--copy-mode-full-buffer nil
562+ " Non-nil when full scrollback has been loaded into the buffer in copy mode." )
563+
559564(defvar-local ghostel--process nil
560565 " The shell process." )
561566
@@ -647,6 +652,7 @@ Used for prompt navigation and optional re-application after full redraws.")
647652 (define-key map (kbd " C-c C-\\ " ) #'ghostel-send-C-backslash )
648653 (define-key map (kbd " C-c C-d" ) #'ghostel-send-C-d )
649654 (define-key map (kbd " C-c C-t" ) #'ghostel-copy-mode )
655+ (define-key map (kbd " C-c M-w" ) #'ghostel-copy-all )
650656 (define-key map (kbd " C-c C-y" ) #'ghostel-paste )
651657 (define-key map (kbd " C-c C-l" ) #'ghostel-clear-scrollback )
652658 (define-key map (kbd " C-c C-q" ) #'ghostel-send-next-key )
@@ -1004,89 +1010,107 @@ pasted using bracketed paste."
10041010(defun ghostel--scroll-up (&optional _event )
10051011 " Scroll the terminal viewport up (into scrollback)."
10061012 (interactive " e" )
1007- (when ghostel--term
1008- (ghostel--scroll ghostel--term -3 )
1009- (if ghostel--copy-mode-active
1010- (let ((inhibit-read-only t ))
1011- (ghostel--redraw ghostel--term ghostel-full-redraw))
1012- (setq ghostel--force-next-redraw t )
1013- (ghostel--invalidate))))
1013+ (if ghostel--copy-mode-full-buffer
1014+ (scroll-down 3 )
1015+ (when ghostel--term
1016+ (ghostel--scroll ghostel--term -3 )
1017+ (if ghostel--copy-mode-active
1018+ (let ((inhibit-read-only t ))
1019+ (ghostel--redraw ghostel--term ghostel-full-redraw))
1020+ (setq ghostel--force-next-redraw t )
1021+ (ghostel--invalidate)))))
10141022
10151023(defun ghostel--scroll-down (&optional _event )
10161024 " Scroll the terminal viewport down."
10171025 (interactive " e" )
1018- (when ghostel--term
1019- (ghostel--scroll ghostel--term 3 )
1020- (if ghostel--copy-mode-active
1021- (let ((inhibit-read-only t ))
1022- (ghostel--redraw ghostel--term ghostel-full-redraw))
1023- (setq ghostel--force-next-redraw t )
1024- (ghostel--invalidate))))
1026+ (if ghostel--copy-mode-full-buffer
1027+ (scroll-up 3 )
1028+ (when ghostel--term
1029+ (ghostel--scroll ghostel--term 3 )
1030+ (if ghostel--copy-mode-active
1031+ (let ((inhibit-read-only t ))
1032+ (ghostel--redraw ghostel--term ghostel-full-redraw))
1033+ (setq ghostel--force-next-redraw t )
1034+ (ghostel--invalidate)))))
10251035
10261036(defun ghostel-copy-mode-scroll-up ()
10271037 " Scroll the terminal viewport up by a page in copy mode."
10281038 (interactive )
1029- (when ghostel--term
1030- (let ((height (count-lines (point-min ) (point-max ))))
1031- (ghostel--scroll ghostel--term (- 2 height))
1032- (let ((inhibit-read-only t ))
1033- (ghostel--redraw ghostel--term ghostel-full-redraw)))))
1039+ (if ghostel--copy-mode-full-buffer
1040+ (scroll-down-command )
1041+ (when ghostel--term
1042+ (let ((height (count-lines (point-min ) (point-max ))))
1043+ (ghostel--scroll ghostel--term (- 2 height))
1044+ (let ((inhibit-read-only t ))
1045+ (ghostel--redraw ghostel--term ghostel-full-redraw))))))
10341046
10351047(defun ghostel-copy-mode-scroll-down ()
10361048 " Scroll the terminal viewport down by a page in copy mode."
10371049 (interactive )
1038- (when ghostel--term
1039- (let ((height (count-lines (point-min ) (point-max ))))
1040- (ghostel--scroll ghostel--term (- height 2 ))
1041- (let ((inhibit-read-only t ))
1042- (ghostel--redraw ghostel--term ghostel-full-redraw)))))
1050+ (if ghostel--copy-mode-full-buffer
1051+ (scroll-up-command )
1052+ (when ghostel--term
1053+ (let ((height (count-lines (point-min ) (point-max ))))
1054+ (ghostel--scroll ghostel--term (- height 2 ))
1055+ (let ((inhibit-read-only t ))
1056+ (ghostel--redraw ghostel--term ghostel-full-redraw))))))
10431057
10441058(defun ghostel-copy-mode-previous-line ()
10451059 " Move to the previous line, scrolling the viewport if at the top."
10461060 (interactive )
10471061 (let ((col (current-column )))
1048- (if (= (line-number-at-pos ) 1 )
1049- (when ghostel--term
1050- (ghostel--scroll ghostel--term -1 )
1051- (let ((inhibit-read-only t ))
1052- (ghostel--redraw ghostel--term ghostel-full-redraw))
1053- (goto-char (point-min )))
1054- (forward-line -1 ))
1062+ (if ghostel--copy-mode-full-buffer
1063+ (forward-line -1 )
1064+ (if (= (line-number-at-pos ) 1 )
1065+ (when ghostel--term
1066+ (ghostel--scroll ghostel--term -1 )
1067+ (let ((inhibit-read-only t ))
1068+ (ghostel--redraw ghostel--term ghostel-full-redraw))
1069+ (goto-char (point-min )))
1070+ (forward-line -1 )))
10551071 (move-to-column col)))
10561072
10571073(defun ghostel-copy-mode-next-line ()
10581074 " Move to the next line, scrolling the viewport if at the bottom."
10591075 (interactive )
10601076 (let ((col (current-column )))
1061- (if (>= (line-number-at-pos ) (line-number-at-pos (point-max )))
1062- (when ghostel--term
1063- (ghostel--scroll ghostel--term 1 )
1064- (let ((inhibit-read-only t ))
1065- (ghostel--redraw ghostel--term ghostel-full-redraw))
1066- (goto-char (point-max ))
1067- (beginning-of-line ))
1068- (forward-line 1 ))
1077+ (if ghostel--copy-mode-full-buffer
1078+ (forward-line 1 )
1079+ (if (>= (line-number-at-pos ) (line-number-at-pos (point-max )))
1080+ (when ghostel--term
1081+ (ghostel--scroll ghostel--term 1 )
1082+ (let ((inhibit-read-only t ))
1083+ (ghostel--redraw ghostel--term ghostel-full-redraw))
1084+ (goto-char (point-max ))
1085+ (beginning-of-line ))
1086+ (forward-line 1 )))
10691087 (move-to-column col)))
10701088
10711089(defun ghostel-copy-mode-beginning-of-buffer ()
10721090 " Scroll to the top of scrollback in copy mode."
10731091 (interactive )
1074- (when ghostel--term
1075- (ghostel--scroll-top ghostel--term)
1076- (let ((inhibit-read-only t ))
1077- (ghostel--redraw ghostel--term ghostel-full-redraw))
1078- (goto-char (point-min ))))
1092+ (if ghostel--copy-mode-full-buffer
1093+ (goto-char (point-min ))
1094+ (when ghostel--term
1095+ (ghostel--scroll-top ghostel--term)
1096+ (let ((inhibit-read-only t ))
1097+ (ghostel--redraw ghostel--term ghostel-full-redraw))
1098+ (goto-char (point-min )))))
10791099
10801100(defun ghostel-copy-mode-end-of-buffer ()
10811101 " Scroll to the bottom of scrollback in copy mode."
10821102 (interactive )
1083- (when ghostel--term
1084- (ghostel--scroll-bottom ghostel--term)
1085- (let ((inhibit-read-only t ))
1086- (ghostel--redraw ghostel--term ghostel-full-redraw))
1087- ; ; The native redraw already positions point at the terminal cursor,
1088- ; ; so no explicit goto-char needed here.
1089- ))
1103+ (if ghostel--copy-mode-full-buffer
1104+ (progn
1105+ (goto-char (point-max ))
1106+ (skip-chars-backward " \t\n " ))
1107+ (when ghostel--term
1108+ (ghostel--scroll-bottom ghostel--term)
1109+ (let ((inhibit-read-only t ))
1110+ (ghostel--redraw ghostel--term ghostel-full-redraw))
1111+ ; ; The native redraw already positions point at the terminal cursor,
1112+ ; ; so no explicit goto-char needed here.
1113+ )))
10901114
10911115(defun ghostel-copy-mode-end-of-line ()
10921116 " Move to the last non-whitespace character on the line."
@@ -1100,30 +1124,32 @@ Scrolls the terminal viewport so the current line is vertically
11001124centered, then redraws. When the scroll is clamped at a scrollback
11011125boundary (nothing to scroll into), does nothing."
11021126 (interactive )
1103- (when ghostel--term
1104- (let* ((current-line (line-number-at-pos ))
1105- (win-height (window-body-height ))
1106- (center (/ win-height 2 ))
1107- (col (current-column )))
1108- (unless (= current-line center)
1109- ; ; Hash the buffer to detect whether the scroll was clamped.
1110- (let ((old-hash (buffer-hash )))
1111- (ghostel--scroll ghostel--term (- current-line center))
1112- (let ((inhibit-read-only t ))
1113- (ghostel--redraw ghostel--term ghostel-full-redraw))
1114- ; ; If the buffer changed the viewport actually moved —
1115- ; ; reposition point at center. Otherwise the scroll was
1116- ; ; clamped; restore point since redraw moved it to the
1117- ; ; terminal cursor.
1118- (if (equal old-hash (buffer-hash ))
1119- (progn
1120- (goto-char (point-min ))
1121- (forward-line (1- current-line))
1122- (move-to-column col))
1123- (goto-char (point-min ))
1124- (forward-line (1- (min center (line-number-at-pos (point-max )))))
1125- (move-to-column col)
1126- (recenter )))))))
1127+ (if ghostel--copy-mode-full-buffer
1128+ (recenter )
1129+ (when ghostel--term
1130+ (let* ((current-line (line-number-at-pos ))
1131+ (win-height (window-body-height ))
1132+ (center (/ win-height 2 ))
1133+ (col (current-column )))
1134+ (unless (= current-line center)
1135+ ; ; Hash the buffer to detect whether the scroll was clamped.
1136+ (let ((old-hash (buffer-hash )))
1137+ (ghostel--scroll ghostel--term (- current-line center))
1138+ (let ((inhibit-read-only t ))
1139+ (ghostel--redraw ghostel--term ghostel-full-redraw))
1140+ ; ; If the buffer changed the viewport actually moved —
1141+ ; ; reposition point at center. Otherwise the scroll was
1142+ ; ; clamped; restore point since redraw moved it to the
1143+ ; ; terminal cursor.
1144+ (if (equal old-hash (buffer-hash ))
1145+ (progn
1146+ (goto-char (point-min ))
1147+ (forward-line (1- current-line))
1148+ (move-to-column col))
1149+ (goto-char (point-min ))
1150+ (forward-line (1- (min center (line-number-at-pos (point-max )))))
1151+ (move-to-column col)
1152+ (recenter ))))))))
11271153
11281154
11291155; ;; Mouse input
@@ -1214,6 +1240,7 @@ boundary (nothing to scroll into), does nothing."
12141240 (define-key map (kbd " M->" ) #'ghostel-copy-mode-end-of-buffer )
12151241 (define-key map (kbd " C-e" ) #'ghostel-copy-mode-end-of-line )
12161242 (define-key map (kbd " C-l" ) #'ghostel-copy-mode-recenter )
1243+ (define-key map (kbd " C-c C-a" ) #'ghostel-copy-mode-load-all )
12171244 map)
12181245 " Keymap for `ghostel-copy-mode' .
12191246Standard Emacs navigation works.
@@ -1259,19 +1286,26 @@ Press \\`q' or \\[ghostel-copy-mode-exit] to exit without copying."
12591286 " Exit copy mode and return to terminal mode."
12601287 (interactive )
12611288 (when ghostel--copy-mode-active
1262- (setq ghostel--copy-mode-active nil )
1263- (setq cursor-type ghostel--saved-cursor-type)
1264- (deactivate-mark )
1265- (use-local-map ghostel--saved-local-map)
1266- (when ghostel--saved-hl-line-mode
1267- (hl-line-mode -1 ))
1268- (setq buffer-read-only nil )
1269- (setq mode-line-process nil )
1270- (force-mode-line-update )
1271- (when ghostel--term
1272- (ghostel--scroll-bottom ghostel--term))
1273- (ghostel--invalidate)
1274- (message " Copy mode exited " )))
1289+ (let ((was-full ghostel--copy-mode-full-buffer))
1290+ (setq ghostel--copy-mode-active nil )
1291+ (setq ghostel--copy-mode-full-buffer nil )
1292+ (setq cursor-type ghostel--saved-cursor-type)
1293+ (deactivate-mark )
1294+ (use-local-map ghostel--saved-local-map)
1295+ (when ghostel--saved-hl-line-mode
1296+ (hl-line-mode -1 ))
1297+ (setq buffer-read-only nil )
1298+ (setq mode-line-process nil )
1299+ (force-mode-line-update )
1300+ (when ghostel--term
1301+ (ghostel--scroll-bottom ghostel--term)
1302+ (when was-full
1303+ ; ; Erase stale full-scrollback content so normal redraw rebuilds
1304+ (let ((inhibit-read-only t ))
1305+ (erase-buffer )
1306+ (ghostel--redraw ghostel--term t ))))
1307+ (ghostel--invalidate)
1308+ (message " Copy mode exited " ))))
12751309
12761310(defun ghostel-copy-mode-exit-and-send ()
12771311 " Exit copy mode and send the key that triggered exit to the terminal."
@@ -1313,6 +1347,34 @@ stripped so the copied text matches the original terminal content."
13131347 (message " Copied to kill ring " )))
13141348 (ghostel-copy-mode-exit))
13151349
1350+ (defun ghostel-copy-mode-load-all ()
1351+ " Load the entire scrollback into the buffer for cross-viewport selection.
1352+ After loading, standard Emacs navigation and selection work across
1353+ the full scrollback history."
1354+ (interactive )
1355+ (when (and ghostel--copy-mode-active ghostel--term
1356+ (not ghostel--copy-mode-full-buffer))
1357+ (message " Loading scrollback... " )
1358+ (let* ((saved-line (1- (line-number-at-pos ))) ; 0-based line within viewport
1359+ (saved-col (current-column ))
1360+ (inhibit-read-only t )
1361+ (viewport-line (ghostel--redraw-full-scrollback ghostel--term)))
1362+ (goto-char (point-min ))
1363+ (forward-line (+ (1- viewport-line) saved-line))
1364+ (move-to-column saved-col)
1365+ (recenter saved-line))
1366+ (setq ghostel--copy-mode-full-buffer t )
1367+ (message " Scrollback loaded " )))
1368+
1369+ (defun ghostel-copy-all ()
1370+ " Copy the entire scrollback buffer to the kill ring."
1371+ (interactive )
1372+ (when ghostel--term
1373+ (let ((text (ghostel--copy-all-text ghostel--term)))
1374+ (when (and text (> (length text) 0 ))
1375+ (kill-new text)
1376+ (message " Copied %d characters to kill ring " (length text))))))
1377+
13161378
13171379; ;; Hyperlinks (OSC 8)
13181380
0 commit comments