1 ;;; vlf.el --- View Large Files -*- lexical-binding: t -*-
3 ;; Copyright (C) 2006, 2012, 2013 Free Software Foundation, Inc.
6 ;; Keywords: large files, utilities
7 ;; Maintainer: Andrey Kotlarski <m00naticus@gmail.com>
8 ;; Authors: 2006 Mathias Dahl <mathias.dahl@gmail.com>
9 ;; 2012 Sam Steingold <sds@gnu.org>
10 ;; 2013 Andrey Kotlarski <m00naticus@gmail.com>
12 ;; This file is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 2, or (at your option)
17 ;; This file is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details.
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs; see the file COPYING. If not, write to
24 ;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25 ;; Boston, MA 02111-1307, USA.
29 ;; This package provides the M-x vlf command, which visits part of a
30 ;; large file without loading the entire file.
31 ;; The buffer uses VLF mode, which defines several commands for
32 ;; moving around, searching and editing selected part of file.
34 ;; This package was inspired by a snippet posted by Kevin Rodgers,
35 ;; showing how to use `insert-file-contents' to extract part of a
41 "View Large Files in Emacs."
45 (defcustom vlf-batch-size 1024
46 "Defines how large each batch of file data is (in bytes)."
50 ;;; Keep track of file position.
51 (defvar vlf-start-pos 0
52 "Absolute position of the visible chunk start.")
53 (defvar vlf-end-pos 0 "Absolute position of the visible chunk end.")
54 (defvar vlf-file-size 0 "Total size of presented file.")
57 (let ((map (make-sparse-keymap)))
58 (define-key map [M-next] 'vlf-next-batch)
59 (define-key map [M-prior] 'vlf-prev-batch)
60 (define-key map "+" 'vlf-change-batch-size)
62 (lambda () "Decrease vlf batch size by factor of 2."
64 (vlf-change-batch-size t)))
65 (define-key map "s" 'vlf-re-search-forward)
66 (define-key map "r" 'vlf-re-search-backward)
67 (define-key map "o" 'vlf-occur)
68 (define-key map "[" 'vlf-beginning-of-file)
69 (define-key map "]" 'vlf-end-of-file)
70 (define-key map "e" 'vlf-edit-mode)
71 (define-key map "j" 'vlf-jump-to-chunk)
72 (define-key map "l" 'vlf-goto-line)
74 "Keymap for `vlf-mode'.")
76 (define-derived-mode vlf-mode special-mode "VLF"
77 "Mode to browse large files in."
78 (setq buffer-read-only t)
79 (set-buffer-modified-p nil)
81 (make-local-variable 'write-file-functions)
82 (add-hook 'write-file-functions 'vlf-write)
83 (make-local-variable 'revert-buffer-function)
84 (setq revert-buffer-function 'vlf-revert)
85 (make-local-variable 'vlf-batch-size)
86 (put 'vlf-batch-size 'permanent-local t)
87 (make-local-variable 'vlf-start-pos)
88 (put 'vlf-start-pos 'permanent-local t)
89 (make-local-variable 'vlf-end-pos)
90 (put 'vlf-end-pos 'permanent-local t)
91 (make-local-variable 'vlf-file-size)
92 (put 'vlf-file-size 'permanent-local t))
97 Batches of the file data from FILE will be displayed in a read-only
98 buffer. You can customize number of bytes displayed by customizing
100 (interactive "fFile to open: ")
101 (with-current-buffer (generate-new-buffer "*vlf*")
103 (setq buffer-file-name file
104 vlf-file-size (vlf-get-file-size file))
106 (switch-to-buffer (current-buffer))))
108 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
109 ;;; integration with other packages
113 "In Dired, visit the file on this line in VLF mode."
115 (vlf (dired-get-file-for-visit)))
118 (eval-after-load "dired"
119 '(define-key dired-mode-map "V" 'dired-vlf))
122 (defun vlf-if-file-too-large (size op-type &optional filename)
123 "If file SIZE larger than `large-file-warning-threshold', \
124 allow user to view file with `vlf', open it normally, or abort.
125 OP-TYPE specifies the file operation being performed over FILENAME."
126 (and large-file-warning-threshold size
127 (> size large-file-warning-threshold)
129 (while (not (memq (setq char
133 "File %s is large (%s): \
134 %s normally (o), %s with vlf (v) or abort (a)"
136 (file-name-nondirectory filename)
138 (file-size-human-readable size)
140 'face 'minibuffer-prompt)))
141 '(?o ?O ?v ?V ?a ?A))))
142 (cond ((memq char '(?o ?O)))
143 ((memq char '(?v ?V))
146 ((memq char '(?a ?A))
147 (error "Aborted"))))))
149 ;; hijack `abort-if-file-too-large'
151 (fset 'abort-if-file-too-large 'vlf-if-file-too-large)
153 ;; scroll auto batching
154 (defadvice scroll-up (around vlf-scroll-up
156 "Slide to next batch if at end of buffer in `vlf-mode'."
157 (if (and (eq major-mode 'vlf-mode)
159 (progn (vlf-next-batch 1)
160 (goto-char (point-min)))
163 (defadvice scroll-down (around vlf-scroll-down
165 "Slide to previous batch if at beginning of buffer in `vlf-mode'."
166 (if (and (eq major-mode 'vlf-mode)
168 (progn (vlf-prev-batch 1)
169 (goto-char (point-max)))
173 (unless (fboundp 'file-size-human-readable)
174 (defun file-size-human-readable (file-size)
175 "Print FILE-SIZE in MB."
176 (format "%.1fMB" (/ file-size 1048576.0))))
178 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
181 (defun vlf-change-batch-size (decrease)
182 "Change the buffer-local value of `vlf-batch-size'.
183 Normally, the value is doubled;
184 with the prefix argument DECREASE it is halved."
186 (or (assq 'vlf-batch-size (buffer-local-variables))
187 (error "%s is not local in this buffer" 'vlf-batch-size))
188 (setq vlf-batch-size (if decrease
190 (* vlf-batch-size 2)))
191 (vlf-move-to-batch vlf-start-pos))
193 (defun vlf-format-buffer-name ()
194 "Return format for vlf buffer name."
195 (format "%s(%s)[%d/%d](%d)"
196 (file-name-nondirectory buffer-file-name)
197 (file-size-human-readable vlf-file-size)
198 (/ vlf-end-pos vlf-batch-size)
199 (/ vlf-file-size vlf-batch-size)
202 (defun vlf-update-buffer-name ()
203 "Update the current buffer name."
204 (rename-buffer (vlf-format-buffer-name) t))
206 (defun vlf-get-file-size (file)
207 "Get size in bytes of FILE."
208 (nth 7 (file-attributes file)))
210 (defun vlf-insert-file (&optional from-end)
211 "Insert first chunk of current file contents in current buffer.
212 With FROM-END prefix, start from the back."
214 (setq vlf-start-pos (max 0 (- vlf-file-size vlf-batch-size))
215 vlf-end-pos vlf-file-size)
216 (setq vlf-start-pos 0
217 vlf-end-pos (min vlf-batch-size vlf-file-size)))
218 (vlf-move-to-chunk vlf-start-pos vlf-end-pos))
220 (defun vlf-beginning-of-file ()
221 "Jump to beginning of file content."
225 (defun vlf-end-of-file ()
226 "Jump to end of file content."
230 (defun vlf-revert (&optional _ignore-auto noconfirm)
231 "Revert current chunk. Ignore IGNORE-AUTO.
232 Ask for confirmation if NOCONFIRM is nil."
234 (yes-or-no-p (format "Revert buffer from file %s? "
236 (vlf-move-to-chunk vlf-start-pos vlf-end-pos)))
238 (defun vlf-jump-to-chunk (n)
240 (interactive "nGoto to chunk: ")
241 (vlf-move-to-batch (* (1- n) vlf-batch-size)))
243 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
246 (defun vlf-next-batch (append)
247 "Display the next batch of file data.
248 When prefix argument is supplied and positive
249 jump over APPEND number of batches.
250 When prefix argument is negative
251 append next APPEND number of batches to the existing buffer."
253 (or (verify-visited-file-modtime (current-buffer))
254 (setq vlf-file-size (vlf-get-file-size buffer-file-name)))
255 (let ((end (min (+ vlf-end-pos (* vlf-batch-size
258 (let ((inhibit-read-only t)
259 (do-append (< append 0))
260 (pos (position-bytes (point))))
262 (goto-char (point-max))
263 (setq vlf-start-pos (- end vlf-batch-size))
265 (insert-file-contents buffer-file-name nil (if do-append
269 (setq vlf-end-pos end)
270 (goto-char (or (byte-to-position (+ pos (vlf-adjust-chunk)))
272 (set-visited-file-modtime)
273 (set-buffer-modified-p nil)
274 (vlf-update-buffer-name))
276 (defun vlf-prev-batch (prepend)
277 "Display the previous batch of file data.
278 When prefix argument is supplied and positive
279 jump over PREPEND number of batches.
280 When prefix argument is negative
281 append previous PREPEND number of batches to the existing buffer."
283 (if (zerop vlf-start-pos)
284 (error "Already at BOF"))
285 (or (verify-visited-file-modtime (current-buffer))
286 (setq vlf-file-size (vlf-get-file-size buffer-file-name)))
287 (let ((inhibit-read-only t)
288 (start (max 0 (- vlf-start-pos (* vlf-batch-size
290 (do-prepend (< prepend 0))
291 (pos (- (position-bytes (point-max))
292 (position-bytes (point)))))
294 (goto-char (point-min))
295 (setq vlf-end-pos (min (+ start vlf-batch-size)
298 (insert-file-contents buffer-file-name nil start
302 (setq vlf-start-pos start)
303 (setq pos (+ pos (vlf-adjust-chunk)))
304 (goto-char (or (byte-to-position (- (position-bytes (point-max))
307 (set-visited-file-modtime)
308 (set-buffer-modified-p nil)
309 (vlf-update-buffer-name))
311 (defun vlf-move-to-batch (start &optional minimal)
312 "Move to batch determined by START.
313 Adjust according to file start/end and show `vlf-batch-size' bytes.
314 When given MINIMAL flag, skip non important operations."
315 (or (verify-visited-file-modtime (current-buffer))
316 (setq vlf-file-size (vlf-get-file-size buffer-file-name)))
317 (setq vlf-start-pos (max 0 start)
318 vlf-end-pos (min (+ vlf-start-pos vlf-batch-size)
320 (if (= vlf-file-size vlf-end-pos) ; re-check file size
321 (setq vlf-start-pos (max 0 (- vlf-end-pos vlf-batch-size))))
322 (let ((inhibit-read-only t)
323 (pos (position-bytes (point))))
325 (insert-file-contents buffer-file-name nil
326 vlf-start-pos vlf-end-pos)
327 (goto-char (or (byte-to-position (+ pos (vlf-adjust-chunk)))
329 (set-buffer-modified-p nil)
330 (set-visited-file-modtime)
331 (or minimal(vlf-update-buffer-name)))
333 (defun vlf-move-to-chunk (start end &optional minimal)
334 "Move to chunk determined by START END.
335 When given MINIMAL flag, skip non important operations."
336 (or (verify-visited-file-modtime (current-buffer))
337 (setq vlf-file-size (vlf-get-file-size buffer-file-name)))
338 (setq vlf-start-pos (max 0 start)
339 vlf-end-pos (min end vlf-file-size))
340 (let ((inhibit-read-only t)
341 (pos (position-bytes (point))))
343 (insert-file-contents buffer-file-name nil
344 vlf-start-pos vlf-end-pos)
345 (goto-char (or (byte-to-position (+ pos (vlf-adjust-chunk)))
347 (set-buffer-modified-p nil)
348 (set-visited-file-modtime)
349 (or minimal (vlf-update-buffer-name)))
351 (defun vlf-adjust-chunk ()
352 "Adjust chunk beginning until content can be properly decoded.
353 Return number of bytes moved back for this to happen."
355 (chunk-size (- vlf-end-pos vlf-start-pos)))
356 (while (and (not (zerop vlf-start-pos))
359 (length (encode-coding-region
360 (point-min) (point-max)
361 buffer-file-coding-system t))))
362 (setq shift (1+ shift)
363 vlf-start-pos (1- vlf-start-pos)
364 chunk-size (1+ chunk-size))
365 (let ((inhibit-read-only t))
367 (insert-file-contents buffer-file-name nil
368 vlf-start-pos vlf-end-pos)))
369 (set-buffer-modified-p nil)
372 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
375 (defun vlf-re-search (regexp count backward batch-step)
376 "Search for REGEXP COUNT number of times forward or BACKWARD.
377 BATCH-STEP is amount of overlap between successive chunks."
379 (let* ((match-chunk-start vlf-start-pos)
380 (match-chunk-end vlf-end-pos)
381 (match-start-pos (+ vlf-start-pos (position-bytes (point))))
382 (match-end-pos match-start-pos)
384 (reporter (make-progress-reporter
385 (concat "Searching for " regexp "...")
387 (- vlf-file-size vlf-end-pos)
393 (while (not (zerop to-find))
394 (cond ((re-search-backward regexp nil t)
395 (setq to-find (1- to-find)
396 match-chunk-start vlf-start-pos
397 match-chunk-end vlf-end-pos
398 match-start-pos (+ vlf-start-pos
400 (match-beginning 0)))
401 match-end-pos (+ vlf-start-pos
404 ((zerop vlf-start-pos)
405 (throw 'end-of-file nil))
406 (t (let ((batch-move (- vlf-start-pos
410 (if (< match-start-pos batch-move)
411 (- match-start-pos vlf-batch-size)
413 (goto-char (if (< match-start-pos
415 (or (byte-to-position
420 (progress-reporter-update
421 reporter (- vlf-file-size
423 (while (not (zerop to-find))
424 (cond ((re-search-forward regexp nil t)
425 (setq to-find (1- to-find)
426 match-chunk-start vlf-start-pos
427 match-chunk-end vlf-end-pos
428 match-start-pos (+ vlf-start-pos
430 (match-beginning 0)))
431 match-end-pos (+ vlf-start-pos
434 ((= vlf-end-pos vlf-file-size)
435 (throw 'end-of-file nil))
436 (t (let ((batch-move (- vlf-end-pos batch-step)))
438 (if (< batch-move match-end-pos)
441 (goto-char (if (< vlf-start-pos match-end-pos)
442 (or (byte-to-position
447 (progress-reporter-update reporter
449 (progress-reporter-done reporter))
451 (vlf-goto-match match-chunk-start match-chunk-end
452 match-end-pos match-start-pos
454 (vlf-goto-match match-chunk-start match-chunk-end
455 match-start-pos match-end-pos
458 (defun vlf-goto-match (match-chunk-start match-chunk-end
462 "Move to MATCH-CHUNK-START MATCH-CHUNK-END surrounding \
463 MATCH-POS-START and MATCH-POS-END.
464 According to COUNT and left TO-FIND, show if search has been
465 successful. Return nil if nothing found."
466 (if (= count to-find)
467 (progn (vlf-move-to-chunk match-chunk-start match-chunk-end)
468 (goto-char (or (byte-to-position (- match-pos-start
471 (message "Not found")
473 (let ((success (zerop to-find)))
475 (vlf-update-buffer-name)
476 (vlf-move-to-chunk match-chunk-start match-chunk-end))
477 (let* ((match-end (or (byte-to-position (- match-pos-end
480 (overlay (make-overlay (byte-to-position
484 (overlay-put overlay 'face 'match)
486 (goto-char match-end)
487 (message "Moved to the %d match which is last"
490 (delete-overlay overlay)
493 (defun vlf-re-search-forward (regexp count)
494 "Search forward for REGEXP prefix COUNT number of times.
495 Search is performed chunk by chunk in `vlf-batch-size' memory."
496 (interactive (list (read-regexp "Search whole file"
498 (car regexp-history)))
499 (or current-prefix-arg 1)))
500 (vlf-re-search regexp count nil (/ vlf-batch-size 8)))
502 (defun vlf-re-search-backward (regexp count)
503 "Search backward for REGEXP prefix COUNT number of times.
504 Search is performed chunk by chunk in `vlf-batch-size' memory."
505 (interactive (list (read-regexp "Search whole file backward"
507 (car regexp-history)))
508 (or current-prefix-arg 1)))
509 (vlf-re-search regexp count t (/ vlf-batch-size 8)))
511 (defun vlf-goto-line (n)
512 "Go to line N. If N is negative, count from the end of file."
513 (interactive "nGo to line: ")
514 (let ((start-pos vlf-start-pos)
515 (end-pos vlf-end-pos)
520 (progn (vlf-beginning-of-file)
521 (goto-char (point-min))
522 (setq success (vlf-re-search "[\n\C-m]" (1- n)
525 (goto-char (point-max))
526 (setq success (vlf-re-search "[\n\C-m]" (- n) t 0)))
528 (message "Onto line %s" n)
529 (vlf-move-to-chunk start-pos end-pos)
532 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
535 (defvar vlf-occur-mode-map
536 (let ((map (make-sparse-keymap)))
537 (define-key map "n" 'vlf-occur-next-match)
538 (define-key map "p" 'vlf-occur-prev-match)
539 (define-key map "\C-m" 'vlf-occur-visit)
540 (define-key map [mouse-1] 'vlf-occur-visit)
541 (define-key map "o" 'vlf-occur-show)
543 "Keymap for command `vlf-occur-mode'.")
545 (define-derived-mode vlf-occur-mode special-mode "VLF[occur]"
546 "Major mode for showing occur matches of VLF opened files.")
548 (defun vlf-occur-next-match ()
549 "Move cursor to next match."
551 (if (eq (get-char-property (point) 'face) 'match)
552 (goto-char (next-single-property-change (point) 'face)))
553 (goto-char (or (text-property-any (point) (point-max) 'face 'match)
554 (text-property-any (point-min) (point)
557 (defun vlf-occur-prev-match ()
558 "Move cursor to previous match."
560 (if (eq (get-char-property (point) 'face) 'match)
561 (goto-char (previous-single-property-change (point) 'face)))
562 (while (not (eq (get-char-property (point) 'face) 'match))
563 (goto-char (or (previous-single-property-change (point) 'face)
566 (defun vlf-occur-show (&optional event)
567 "Visit current `vlf-occur' link in a vlf buffer but stay in the \
568 occur buffer. If original VLF buffer has been killed,
569 open new VLF session each time.
570 EVENT may hold details of the invocation."
571 (interactive (list last-nonmenu-event))
572 (let ((occur-buffer (if event
573 (window-buffer (posn-window
576 (vlf-occur-visit event)
577 (pop-to-buffer occur-buffer)))
579 (defun vlf-occur-visit (&optional event)
580 "Visit current `vlf-occur' link in a vlf buffer.
581 If original VLF buffer has been killed,
582 open new VLF session each time.
583 EVENT may hold details of the invocation."
584 (interactive (list last-nonmenu-event))
586 (set-buffer (window-buffer (posn-window (event-end event))))
587 (goto-char (posn-point (event-end event))))
589 (pos-relative (- pos (line-beginning-position) 1))
590 (file (get-char-property pos 'file)))
592 (let ((chunk-start (get-char-property pos 'chunk-start))
593 (chunk-end (get-char-property pos 'chunk-end))
594 (buffer (get-char-property pos 'buffer))
595 (match-pos (+ (get-char-property pos 'line-pos)
597 (or (buffer-live-p buffer)
598 (let ((occur-buffer (current-buffer)))
599 (setq buffer (vlf file))
600 (switch-to-buffer occur-buffer)))
601 (pop-to-buffer buffer)
602 (if (buffer-modified-p)
603 (cond ((and (= vlf-start-pos chunk-start)
604 (= vlf-end-pos chunk-end))
605 (goto-char match-pos))
606 ((y-or-n-p "VLF buffer has been modified. \
607 Really jump to new chunk? ")
608 (vlf-move-to-chunk chunk-start chunk-end)
609 (goto-char match-pos)))
610 (vlf-move-to-chunk chunk-start chunk-end)
611 (goto-char match-pos))))))
613 (defun vlf-occur (regexp)
614 "Make whole file occur style index for REGEXP.
615 Prematurely ending indexing will still show what's found so far."
616 (interactive (list (read-regexp "List lines matching regexp"
618 (car regexp-history)))))
619 (let ((start-pos vlf-start-pos)
620 (end-pos vlf-end-pos)
622 (vlf-beginning-of-file)
623 (goto-char (point-min))
624 (unwind-protect (vlf-build-occur regexp)
625 (vlf-move-to-chunk start-pos end-pos)
628 (defun vlf-build-occur (regexp)
629 "Build occur style index for REGEXP."
632 (last-line-pos (point-min))
633 (file buffer-file-name)
635 (match-end-pos (+ vlf-start-pos (position-bytes (point))))
636 (occur-buffer (generate-new-buffer
637 (concat "*VLF-occur " (file-name-nondirectory
640 (line-regexp (concat "\\(?5:[\n\C-m]\\)\\|\\(?10:"
642 (batch-step (/ vlf-batch-size 8))
644 (reporter (make-progress-reporter
645 (concat "Building index for " regexp "...")
646 vlf-start-pos vlf-file-size)))
649 (while (not end-of-file)
650 (if (re-search-forward line-regexp nil t)
652 (setq match-end-pos (+ vlf-start-pos
656 (setq line (1+ line) ; line detected
657 last-line-pos (point))
658 (let* ((chunk-start vlf-start-pos)
659 (chunk-end vlf-end-pos)
660 (vlf-buffer (current-buffer))
661 (line-pos (line-beginning-position))
662 (line-text (buffer-substring
663 line-pos (line-end-position))))
664 (with-current-buffer occur-buffer
665 (unless (= line last-match-line) ;new match line
666 (insert "\n:") ; insert line number
667 (let* ((overlay-pos (1- (point)))
668 (overlay (make-overlay
671 (overlay-put overlay 'before-string
673 (number-to-string line)
675 (insert (propertize line-text ; insert line
678 'chunk-start chunk-start
680 'mouse-face '(highlight)
683 (format "Move to line %d"
685 (setq last-match-line line
686 total-matches (1+ total-matches))
687 (let ((line-start (1+
688 (line-beginning-position)))
689 (match-pos (match-beginning 10)))
690 (add-text-properties ; mark match
691 (+ line-start match-pos (- last-line-pos))
692 (+ line-start (match-end 10)
696 (format "Move to match %d"
697 total-matches))))))))
698 (setq end-of-file (= vlf-end-pos vlf-file-size))
700 (let ((batch-move (- vlf-end-pos batch-step)))
701 (vlf-move-to-batch (if (< batch-move match-end-pos)
704 (goto-char (if (< vlf-start-pos match-end-pos)
705 (or (byte-to-position (- match-end-pos
709 (setq last-match-line 0
710 last-line-pos (line-beginning-position))
711 (progress-reporter-update reporter vlf-end-pos))))
712 (progress-reporter-done reporter))
713 (if (zerop total-matches)
714 (progn (with-current-buffer occur-buffer
715 (set-buffer-modified-p nil))
716 (kill-buffer occur-buffer)
717 (message "No matches for \"%s\"" regexp))
718 (with-current-buffer occur-buffer
719 (goto-char (point-min))
721 (format "%d matches from %d lines for \"%s\" \
722 in file: %s" total-matches line regexp file)
724 (set-buffer-modified-p nil)
727 (display-buffer occur-buffer)))))
729 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
732 (defvar vlf-edit-mode-map
733 (let ((map (make-sparse-keymap)))
734 (set-keymap-parent map text-mode-map)
735 (define-key map "\C-c\C-c" 'vlf-write)
736 (define-key map "\C-c\C-q" 'vlf-discard-edit)
737 (define-key map "\C-v" vlf-mode-map)
739 "Keymap for command `vlf-edit-mode'.")
741 (define-derived-mode vlf-edit-mode vlf-mode "VLF[edit]"
742 "Major mode for editing large file chunks."
743 (setq buffer-read-only nil)
745 (message (substitute-command-keys
746 "Editing: Type \\[vlf-write] to write chunk \
747 or \\[vlf-discard-edit] to discard changes.")))
749 (defun vlf-discard-edit ()
750 "Discard edit and refresh chunk from file."
752 (set-buffer-modified-p nil)
753 (vlf-move-to-chunk vlf-start-pos vlf-end-pos)
755 (message "Switched to VLF mode."))
757 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
761 "Write current chunk to file. Always return true to disable save.
762 If changing size of chunk shift remaining file content."
764 (when (and (buffer-modified-p)
765 (or (verify-visited-file-modtime (current-buffer))
766 (y-or-n-p "File has changed since visited or saved. \
769 (size-change (- vlf-end-pos vlf-start-pos
770 (length (encode-coding-region
771 (point-min) (point-max)
772 buffer-file-coding-system t)))))
773 (cond ((zerop size-change)
774 (write-region nil nil buffer-file-name vlf-start-pos t))
776 (vlf-file-shift-back size-change))
777 (t (vlf-file-shift-forward (- size-change))))
778 (vlf-move-to-chunk vlf-start-pos vlf-end-pos)
783 (defun vlf-file-shift-back (size-change)
784 "Shift file contents SIZE-CHANGE bytes back."
785 (write-region nil nil buffer-file-name vlf-start-pos t)
786 (buffer-disable-undo)
787 (let ((read-start-pos vlf-end-pos)
788 (coding-system-for-write 'no-conversion)
789 (reporter (make-progress-reporter "Adjusting file content..."
792 (while (vlf-shift-batch read-start-pos (- read-start-pos
794 (setq read-start-pos (+ read-start-pos vlf-batch-size))
795 (progress-reporter-update reporter read-start-pos))
796 ;; pad end with space
798 (insert-char 32 size-change)
799 (write-region nil nil buffer-file-name (- vlf-file-size
801 (progress-reporter-done reporter)))
803 (defun vlf-shift-batch (read-pos write-pos)
804 "Read `vlf-batch-size' bytes from READ-POS and write them \
805 back at WRITE-POS. Return nil if EOF is reached, t otherwise."
807 (or (verify-visited-file-modtime (current-buffer))
808 (setq vlf-file-size (vlf-get-file-size buffer-file-name)))
809 (let ((read-end (+ read-pos vlf-batch-size)))
810 (insert-file-contents-literally buffer-file-name nil
812 (min vlf-file-size read-end))
813 (write-region nil nil buffer-file-name write-pos 0)
814 (< read-end vlf-file-size)))
816 (defun vlf-file-shift-forward (size-change)
817 "Shift file contents SIZE-CHANGE bytes forward.
818 Done by saving content up front and then writing previous batch."
819 (buffer-disable-undo)
820 (let ((size (+ vlf-batch-size size-change))
821 (read-pos vlf-end-pos)
822 (write-pos vlf-start-pos)
823 (reporter (make-progress-reporter "Adjusting file content..."
826 (when (vlf-shift-batches size read-pos write-pos t)
827 (setq write-pos (+ read-pos size-change)
828 read-pos (+ read-pos size))
829 (progress-reporter-update reporter write-pos)
830 (let ((coding-system-for-write 'no-conversion))
831 (while (vlf-shift-batches size read-pos write-pos nil)
832 (setq write-pos (+ read-pos size-change)
833 read-pos (+ read-pos size))
834 (progress-reporter-update reporter write-pos))))
835 (progress-reporter-done reporter)))
837 (defun vlf-shift-batches (size read-pos write-pos hide-read)
838 "Append SIZE bytes of file starting at READ-POS.
839 Then write initial buffer content to file at WRITE-POS.
840 If HIDE-READ is non nil, temporarily hide literal read content.
841 Return nil if EOF is reached, t otherwise."
842 (or (verify-visited-file-modtime (current-buffer))
843 (setq vlf-file-size (vlf-get-file-size buffer-file-name)))
844 (let ((read-more (< read-pos vlf-file-size))
845 (start-write-pos (point-min))
846 (end-write-pos (point-max)))
848 (goto-char end-write-pos)
849 (insert-file-contents-literally buffer-file-name nil read-pos
850 (min vlf-file-size (+ read-pos
853 (if hide-read ; hide literal region if user has to choose encoding
854 (narrow-to-region start-write-pos end-write-pos))
855 (write-region start-write-pos end-write-pos
856 buffer-file-name write-pos 0)
857 (delete-region start-write-pos end-write-pos)
858 (if hide-read (widen))