1
0
mirror of https://github.com/m00natic/vlfi.git synced 2025-11-14 13:55:33 +00:00

56 Commits
0.4 ... 0.7

Author SHA1 Message Date
Andrey Kotlarski
b34bd2e5fb Don't use temporary buffer when adjusting expanded file content. 2013-04-15 23:38:14 +03:00
Andrey Kotlarski
1589cf9736 Make custom revert function local only for vlfi buffers. 2013-04-15 17:42:11 +03:00
Andrey Kotlarski
83eb8a586b Fixes for GNU Emacs 23. 2013-04-15 17:27:23 +03:00
Andrey Kotlarski
e3be8d5d98 Don't adjust chunk with more that 3 bytes and inhibit spurious update
messages when saving content with changes size.
2013-04-15 15:04:22 +03:00
Andrey Kotlarski
5aef3e1c27 Update file size changes more lazily. 2013-04-15 01:26:58 +03:00
Andrey Kotlarski
ca8ba42ec4 Fix position handling to correctly deal with multibyte characters. 2013-04-15 00:57:10 +03:00
Andrey Kotlarski
a31be70613 Update README and bump version. 2013-04-14 20:01:10 +03:00
Andrey Kotlarski
538a9e6049 Apply chunk decoding adjustment implicitly. 2013-04-14 19:48:20 +03:00
Andrey Kotlarski
469900878d Make chunk adjusting automatic. 2013-04-14 19:28:41 +03:00
Andrey Kotlarski
473536e050 Add adjust command for cases where chunk starts with part of multibyte symbol. 2013-04-14 18:58:30 +03:00
Andrey Kotlarski
74febeb183 Remove ability to launch VLFI from file end. It's superseded by
vlfi-end-of-file and may break decoding of file.
2013-04-14 18:27:31 +03:00
Andrey Kotlarski
fc689d5c88 Make coding system conversion on write more idiomatic. 2013-04-14 18:19:49 +03:00
Andrey Kotlarski
3fb898e83a Add automatic batching when scrolling. 2013-04-14 02:50:31 +03:00
Andrey Kotlarski
f1ade8106c Add go to line command. 2013-04-14 02:21:49 +03:00
Andrey Kotlarski
e67895afc4 Optionally strip superfluous operations when inserting batches. 2013-04-14 01:09:13 +03:00
Andrey Kotlarski
4cbefdeeca Optimize chunk save buffers for multiple insert/erase operations. 2013-04-14 00:54:16 +03:00
Andrey Kotlarski
9fad430601 Fix positioning after save. 2013-04-14 00:53:24 +03:00
Andrey Kotlarski
6ea7a2aa1e Add macro for file size determination. 2013-04-14 00:52:07 +03:00
Andrey Kotlarski
705f9ce0eb Add issue section to the README. 2013-04-13 23:45:30 +03:00
Andrey Kotlarski
b52ca6c044 Don't change encoding for edited chunk, leave it to the user in case
of problem characters.
2013-04-13 23:45:06 +03:00
Andrey Kotlarski
8637ce5ceb Update README and version. 2013-04-13 23:10:47 +03:00
Andrey Kotlarski
2f201c56d5 Add jump to chunk command. 2013-04-13 22:57:09 +03:00
Andrey Kotlarski
d5f2a36086 Rearrange code in sections. 2013-04-13 22:49:08 +03:00
Andrey Kotlarski
cedd0b4e82 Fix spaces and indent. 2013-04-13 22:36:33 +03:00
Andrey Kotlarski
c14c7f00ce Add shift forward of file contents when edited chunk grows in size. 2013-04-13 22:34:54 +03:00
Andrey Kotlarski
f23262e826 Add shift back of file contents when edited chunk shrinks in size. 2013-04-13 20:47:29 +03:00
Andrey Kotlarski
8f6299c6c2 Simplify detection of buffer size change. 2013-04-13 01:29:12 +03:00
Andrey Kotlarski
8787186619 Show paging according to batch size instead of percentage. 2013-04-12 01:01:42 +03:00
Andrey Kotlarski
24675d8741 Manage modification times when attempting to save. 2013-04-12 00:21:14 +03:00
Andrey Kotlarski
e115e7e56a Add custom revert function. 2013-04-11 23:00:34 +03:00
Andrey Kotlarski
7985f0f453 Fix `vlfi-if-file-too-large' to be more tolerable on GNU Emacs 23. 2013-04-11 18:36:39 +03:00
Andrey Kotlarski
a3c405fd37 Make detection of buffer size change on save more correct when dealing
with Unicode.
2013-04-11 18:08:34 +03:00
Andrey Kotlarski
56134a8281 Make vlfi usable under GNU Emacs 23. 2013-04-11 17:45:35 +03:00
Andrey Kotlarski
2525ebf069 Improvements to file saving. 2013-04-11 17:44:32 +03:00
Andrey Kotlarski
c4f87f9ec7 Move save interception to vlfi-mode. 2013-04-11 16:53:38 +03:00
Andrey Kotlarski
467154ad06 Add documentation. 2013-04-10 02:10:00 +03:00
Andrey Kotlarski
06108220e2 Make saving with change of size correct. 2013-04-10 02:08:54 +03:00
Andrey Kotlarski
5405a30de4 Intercept buffer saving with vlfi-write. 2013-04-08 23:24:48 +03:00
Andrey Kotlarski
f3132c0970 Keep current position when moving around chunks. 2013-04-08 12:49:17 +03:00
Andrey Kotlarski
db3d2af04a Update version, README and commentary section. 2013-04-08 01:08:15 +03:00
Andrey Kotlarski
995a7a0988 Change some VLFI key-bindings not to override special-mode ones. 2013-04-08 00:57:53 +03:00
Andrey Kotlarski
1948f3ea4e Make VLFI edit mode actually working. 2013-04-08 00:56:42 +03:00
Andrey Kotlarski
0ff2f3eb9b Merge branch 'master' into edit 2013-04-07 18:54:40 +03:00
Andrey Kotlarski
03f323337b Don't reinsert content and move on successful search. 2013-04-07 18:53:50 +03:00
Andrey Kotlarski
9abee0425a Merge branch 'master' into edit 2013-04-05 00:43:15 +03:00
Andrey Kotlarski
80ca6b638b Fix vlfi invocation when prompted for large files. 2013-04-04 11:49:19 +03:00
Andrey Kotlarski
8a33dbfb89 Add something like edit minor mode. 2013-04-02 02:55:53 +03:00
Andrey Kotlarski
e387ed5f09 Add forgotten local variable statement and documentation. 2013-04-02 01:46:45 +03:00
Andrey Kotlarski
c36db11b86 Return correct search success status. 2013-04-01 14:38:50 +03:00
Andrey Kotlarski
ab20671a93 Abstract batch overlapping during search and optimize a bit. 2013-04-01 14:32:10 +03:00
Andrey Kotlarski
ac382e90dc Add temporary highlight of match. 2013-04-01 12:57:58 +03:00
Andrey Kotlarski
670561e811 Optimize search a bit. 2013-04-01 12:06:48 +03:00
Andrey Kotlarski
88cf03caba Interactive change of batch size immediately updates content. 2013-04-01 11:47:49 +03:00
Andrey Kotlarski
8e65e13dd9 Refactor and simplify search procedures. Always use batch sized
chunks.
2013-04-01 03:09:32 +03:00
Andrey Kotlarski
090cebc2ab Add procedure to insert strictly batch sized chunk (if possible). 2013-04-01 03:08:14 +03:00
Andrey Kotlarski
748fd1406e More key-binding simplification. 2013-04-01 03:07:20 +03:00
2 changed files with 557 additions and 270 deletions

View File

@@ -1,12 +1,20 @@
* View Large File Improved
* View Large Files Improved
An Emacs mode that allows viewing files in chunks. This is a fork
that builds on the GNU ELPA vlf.el. It adds the following
improvements:
An Emacs mode that allows viewing, editing and searching in large
files in chunks. Batch size can be adjusted on the fly and bounds the
memory that is to be used for operations on the file.
- by chunk search
- options to jump to end or beginning of file
This is a fork that builds on the bare bones GNU ELPA vlf.el. It adds
the following improvements:
- proper dealing with Unicode
- regular expression search on whole file (in constant memory
determined by current batch size)
- chunk editing (if size has changed, saving is done in constant
memory determined by current batch size)
- options to jump to beginning, end or arbitrary file chunk
- ability to jump/insert given number of batches at once
- ability to view newly added content if the file has grown meanwhile
- newly added content is acknowledged if file has changed size
meanwhile
- automatic scrolling of batches
- vlfi is added as an option when opening large files

803
vlfi.el
View File

@@ -1,9 +1,8 @@
;;; vlfi.el --- View Large Files Improved
;;; -*- lexical-bind: t -*-
;;; vlfi.el --- View Large Files Improved -*- lexical-binding: t -*-
;; Copyright (C) 2006, 2012, 2013 Free Software Foundation, Inc.
;; Version: 0.4
;; Version: 0.7
;; Keywords: large files, utilities
;; Authors: 2006 Mathias Dahl <mathias.dahl@gmail.com>
;; 2012 Sam Steingold <sds@gnu.org>
@@ -28,10 +27,8 @@
;; This package provides the M-x vlfi command, which visits part of a
;; large file in a read-only buffer without visiting the entire file.
;; The buffer uses VLFI mode, which defines the commands M-<next>
;; (vlfi-next-batch) and M-<prior> (vlfi-prev-batch) to visit other
;; parts of the file. The option `vlfi-batch-size' specifies the size
;; of each batch, in bytes.
;; The buffer uses VLFI mode, which defines several commands for
;; moving around, searching and editing selected chunk of file.
;; This package is an improved fork of the vlf.el package.
@@ -47,28 +44,29 @@
:type 'integer
:group 'vlfi)
;; Keep track of file position.
(defvar vlfi-start-pos)
(defvar vlfi-end-pos)
(defvar vlfi-file-size)
;;; Keep track of file position.
(defvar vlfi-start-pos 0
"Absolute position of the visible chunk start.")
(defvar vlfi-end-pos vlfi-batch-size
"Absolute position of the visible chunk end.")
(defvar vlfi-file-size 0 "Total size of presented file.")
(defvar vlfi-mode-map
(let ((map (make-sparse-keymap)))
(define-key map [M-next] 'vlfi-next-batch)
(define-key map [M-prior] 'vlfi-prev-batch)
(define-key map (kbd "M-+") 'vlfi-change-batch-size)
(define-key map (kbd "M--")
(define-key map "+" 'vlfi-change-batch-size)
(define-key map "-"
(lambda () "Decrease vlfi batch size by factor of 2."
(interactive)
(vlfi-change-batch-size t)))
(define-key map "s" 'vlfi-re-search-forward)
(define-key map "r" 'vlfi-re-search-backward)
(define-key map ">" (lambda () "Jump to end of file content."
(interactive)
(vlfi-insert-file buffer-file-name t)))
(define-key map "<" (lambda () "Jump to beginning of file content."
(interactive)
(vlfi-insert-file buffer-file-name)))
(define-key map "[" 'vlfi-beginning-of-file)
(define-key map "]" 'vlfi-end-of-file)
(define-key map "e" 'vlfi-edit-mode)
(define-key map "j" 'vlfi-jump-to-chunk)
(define-key map "l" 'vlfi-goto-line)
map)
"Keymap for `vlfi-mode'.")
@@ -76,139 +74,41 @@
"Mode to browse large files in."
(setq buffer-read-only t)
(set-buffer-modified-p nil)
(buffer-disable-undo)
(add-hook 'write-contents-functions 'vlfi-write)
(make-local-variable 'revert-buffer-function)
(setq revert-buffer-function 'vlfi-revert)
(make-local-variable 'vlfi-batch-size)
(put 'vlfi-batch-size 'permanent-local t)
(make-local-variable 'vlfi-start-pos)
(make-local-variable 'vlfi-file-size))
(defun vlfi-change-batch-size (decrease)
"Change the buffer-local value of `vlfi-batch-size'.
Normally, the value is doubled;
with the prefix argument DECREASE it is halved."
(interactive "P")
(or (assq 'vlfi-batch-size (buffer-local-variables))
(error "%s is not local in this buffer" 'vlfi-batch-size))
(setq vlfi-batch-size
(if decrease
(/ vlfi-batch-size 2)
(* vlfi-batch-size 2)))
(vlfi-update-buffer-name))
(defun vlfi-format-buffer-name ()
"Return format for vlfi buffer name."
(format "%s(%s)[%.2f%%%%](%d)"
(file-name-nondirectory buffer-file-name)
(file-size-human-readable vlfi-file-size)
(/ (* 100 vlfi-end-pos) (float vlfi-file-size))
vlfi-batch-size))
(defun vlfi-update-buffer-name ()
"Update the current buffer name."
(rename-buffer (vlfi-format-buffer-name) t))
(defun vlfi-next-batch (append)
"Display the next batch of file data.
When prefix argument is supplied and positive
jump over APPEND number of batches.
When prefix argument is negative
append next APPEND number of batches to the existing buffer."
(interactive "p")
(let ((end (+ vlfi-end-pos (* vlfi-batch-size
(abs append)))))
(when (< vlfi-file-size end) ; re-check file size
(setq vlfi-file-size (nth 7 (file-attributes buffer-file-name)))
(cond ((= vlfi-end-pos vlfi-file-size)
(error "Already at EOF"))
((< vlfi-file-size end)
(setq end vlfi-file-size))))
(let ((inhibit-read-only t)
(do-append (< append 0))
(pos (point)))
(if do-append
(goto-char (point-max))
(setq vlfi-start-pos (- end vlfi-batch-size))
(erase-buffer))
(insert-file-contents buffer-file-name nil
(if do-append
vlfi-end-pos
vlfi-start-pos)
end)
(goto-char pos))
(setq vlfi-end-pos end))
(set-buffer-modified-p nil)
(vlfi-update-buffer-name))
(defun vlfi-prev-batch (prepend)
"Display the previous batch of file data.
When prefix argument is supplied and positive
jump over PREPEND number of batches.
When prefix argument is negative
append previous PREPEND number of batches to the existing buffer."
(interactive "p")
(if (zerop vlfi-start-pos)
(error "Already at BOF"))
(let ((inhibit-read-only t)
(start (max 0 (- vlfi-start-pos (* vlfi-batch-size
(abs prepend)))))
(do-prepend (< prepend 0))
(pos (- (point-max) (point))))
(if do-prepend
(goto-char (point-min))
(setq vlfi-end-pos (+ start vlfi-batch-size))
(erase-buffer))
(insert-file-contents buffer-file-name nil start
(if do-prepend
vlfi-start-pos
vlfi-end-pos))
(goto-char (- (point-max) pos))
(setq vlfi-start-pos start))
(set-buffer-modified-p nil)
(vlfi-update-buffer-name))
(defun vlfi-move-to-chunk (start end)
"Move to chunk determined by START END."
(if (< vlfi-file-size end) ; re-check file size
(setq vlfi-file-size (nth 7
(file-attributes buffer-file-name))))
(setq vlfi-start-pos (max 0 start)
vlfi-end-pos (min end vlfi-file-size))
(let ((inhibit-read-only t))
(erase-buffer)
(insert-file-contents buffer-file-name nil
vlfi-start-pos vlfi-end-pos))
(set-buffer-modified-p nil)
(vlfi-update-buffer-name))
(defun vlfi-insert-file (file &optional from-end)
"Insert first chunk of FILE contents in current buffer.
With FROM-END prefix, start from the back."
(if from-end
(setq vlfi-start-pos (max 0 (- vlfi-file-size vlfi-batch-size))
vlfi-end-pos vlfi-file-size)
(setq vlfi-start-pos 0
vlfi-end-pos (min vlfi-batch-size vlfi-file-size)))
(vlfi-move-to-chunk vlfi-start-pos vlfi-end-pos))
(put 'vlfi-start-pos 'permanent-local t)
(make-local-variable 'vlfi-end-pos)
(put 'vlfi-end-pos 'permanent-local t)
(make-local-variable 'vlfi-file-size)
(put 'vlfi-file-size 'permanent-local t))
;;;###autoload
(defun vlfi (file &optional from-end)
"View Large FILE. With FROM-END prefix, view from the back.
(defun vlfi (file)
"View Large FILE.
Batches of the file data from FILE will be displayed in a read-only
buffer. You can customize number of bytes displayed by customizing
`vlfi-batch-size'."
(interactive "fFile to open: \nP")
(interactive "fFile to open: ")
(with-current-buffer (generate-new-buffer "*vlfi*")
(buffer-disable-undo)
(setq buffer-file-name file
vlfi-file-size (nth 7 (file-attributes file)))
(vlfi-insert-file file from-end)
vlfi-file-size (vlfi-get-file-size file))
(vlfi-insert-file)
(vlfi-mode)
(switch-to-buffer (current-buffer))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; integration with other packages
;;;###autoload
(defun dired-vlfi (from-end)
"In Dired, visit the file on this line in VLFI mode.
With FROM-END prefix, view from the back."
(interactive "P")
(vlfi (dired-get-file-for-visit) from-end))
(defun dired-vlfi ()
"In Dired, visit the file on this line in VLFI mode."
(interactive)
(vlfi (dired-get-file-for-visit)))
;;;###autoload
(eval-after-load "dired"
@@ -225,149 +125,528 @@ OP-TYPE specifies the file operation being performed over FILENAME."
(while (not (memq (setq char
(read-event
(propertize
(format "File %s is large (%s): \
(format
"File %s is large (%s): \
%s normally (o), %s with vlfi (v) or abort (a)"
(file-name-nondirectory filename)
(file-size-human-readable size)
op-type op-type)
(if filename
(file-name-nondirectory filename)
"")
(file-size-human-readable size)
op-type op-type)
'face 'minibuffer-prompt)))
'(?o ?O ?v ?V ?a ?A))))
(cond ((memq char '(?o ?O)))
((memq char '(?v ?V))
(vlfi nil filename)
(vlfi filename)
(error ""))
((memq char '(?a ?A))
(error "Aborted"))))))
;;; hijack `abort-if-file-too-large'
;; hijack `abort-if-file-too-large'
;;;###autoload
(fset 'abort-if-file-too-large 'vlfi-if-file-too-large)
(defun vlfi-re-search-forward (regexp count)
"Search forward for REGEXP prefix COUNT number of times."
(interactive (list (read-regexp "Search whole file"
(if regexp-history
(car regexp-history))
'regexp-history)
(or current-prefix-arg 1)))
(let ((match-chunk-start vlfi-start-pos)
(match-chunk-end vlfi-end-pos)
(match-start-pos (point))
(match-end-pos (point))
(to-find count)
(search-reporter (make-progress-reporter
(concat "Searching for " regexp)
vlfi-start-pos vlfi-file-size))
(initial-chunk t))
;; scroll auto batching
(defadvice scroll-up (around vlfi-scroll-up
activate compile)
"Slide to next batch if at end of buffer in `vlfi-mode'."
(if (and (eq major-mode 'vlfi-mode)
(eobp))
(progn (vlfi-next-batch 1)
(goto-char (point-min)))
ad-do-it))
(defadvice scroll-down (around vlfi-scroll-down
activate compile)
"Slide to previous batch if at beginning of buffer in `vlfi-mode'."
(if (and (eq major-mode 'vlfi-mode)
(bobp))
(progn (vlfi-prev-batch 1)
(goto-char (point-max)))
ad-do-it))
;; non recent Emacs
(unless (fboundp 'file-size-human-readable)
(defun file-size-human-readable (file-size)
"Print FILE-SIZE in MB."
(format "%.1fMB" (/ file-size 1024.0))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; utilities
(defun vlfi-change-batch-size (decrease)
"Change the buffer-local value of `vlfi-batch-size'.
Normally, the value is doubled;
with the prefix argument DECREASE it is halved."
(interactive "P")
(or (assq 'vlfi-batch-size (buffer-local-variables))
(error "%s is not local in this buffer" 'vlfi-batch-size))
(setq vlfi-batch-size (if decrease
(/ vlfi-batch-size 2)
(* vlfi-batch-size 2)))
(vlfi-move-to-batch vlfi-start-pos))
(defun vlfi-format-buffer-name ()
"Return format for vlfi buffer name."
(format "%s(%s)[%d/%d](%d)"
(file-name-nondirectory buffer-file-name)
(file-size-human-readable vlfi-file-size)
(/ vlfi-end-pos vlfi-batch-size)
(/ vlfi-file-size vlfi-batch-size)
vlfi-batch-size))
(defun vlfi-update-buffer-name ()
"Update the current buffer name."
(rename-buffer (vlfi-format-buffer-name) t))
(defmacro vlfi-get-file-size (file)
"Get size in bytes of FILE."
`(nth 7 (file-attributes ,file)))
(defun vlfi-insert-file (&optional from-end)
"Insert first chunk of current file contents in current buffer.
With FROM-END prefix, start from the back."
(if from-end
(setq vlfi-start-pos (max 0 (- vlfi-file-size vlfi-batch-size))
vlfi-end-pos vlfi-file-size)
(setq vlfi-start-pos 0
vlfi-end-pos (min vlfi-batch-size vlfi-file-size)))
(vlfi-move-to-chunk vlfi-start-pos vlfi-end-pos))
(defun vlfi-beginning-of-file ()
"Jump to beginning of file content."
(interactive)
(vlfi-insert-file))
(defun vlfi-end-of-file ()
"Jump to end of file content."
(interactive)
(vlfi-insert-file t))
(defun vlfi-revert (&optional ignore-auto noconfirm)
"Revert current chunk. Ignore IGNORE-AUTO.
Ask for confirmation if NOCONFIRM is nil."
(ignore ignore-auto)
(or noconfirm
(yes-or-no-p (format "Revert buffer from file %s? "
buffer-file-name))
(vlfi-move-to-chunk vlfi-start-pos vlfi-end-pos)))
(defun vlfi-jump-to-chunk (n)
"Go to to chunk N."
(interactive "nGoto to chunk: ")
(vlfi-move-to-batch (* (1- n) vlfi-batch-size)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; batch movement
(defun vlfi-next-batch (append)
"Display the next batch of file data.
When prefix argument is supplied and positive
jump over APPEND number of batches.
When prefix argument is negative
append next APPEND number of batches to the existing buffer."
(interactive "p")
(or (verify-visited-file-modtime (current-buffer))
(setq vlfi-file-size (vlfi-get-file-size buffer-file-name)))
(let ((end (min (+ vlfi-end-pos (* vlfi-batch-size
(abs append)))
vlfi-file-size)))
(let ((inhibit-read-only t)
(do-append (< append 0))
(pos (position-bytes (point))))
(if do-append
(goto-char (point-max))
(setq vlfi-start-pos (- end vlfi-batch-size))
(erase-buffer))
(insert-file-contents buffer-file-name nil (if do-append
vlfi-end-pos
vlfi-start-pos)
end)
(setq vlfi-end-pos end)
(goto-char (or (byte-to-position (+ pos (vlfi-adjust-chunk)))
(point-max)))))
(set-visited-file-modtime)
(set-buffer-modified-p nil)
(vlfi-update-buffer-name))
(defun vlfi-prev-batch (prepend)
"Display the previous batch of file data.
When prefix argument is supplied and positive
jump over PREPEND number of batches.
When prefix argument is negative
append previous PREPEND number of batches to the existing buffer."
(interactive "p")
(if (zerop vlfi-start-pos)
(error "Already at BOF"))
(or (verify-visited-file-modtime (current-buffer))
(setq vlfi-file-size (vlfi-get-file-size buffer-file-name)))
(let ((inhibit-read-only t)
(start (max 0 (- vlfi-start-pos (* vlfi-batch-size
(abs prepend)))))
(do-prepend (< prepend 0))
(pos (- (position-bytes (point-max))
(position-bytes (point)))))
(if do-prepend
(goto-char (point-min))
(setq vlfi-end-pos (min (+ start vlfi-batch-size)
vlfi-file-size))
(erase-buffer))
(insert-file-contents buffer-file-name nil start
(if do-prepend
vlfi-start-pos
vlfi-end-pos))
(setq vlfi-start-pos start)
(setq pos (+ pos (vlfi-adjust-chunk)))
(goto-char (or (byte-to-position (- (position-bytes (point-max))
pos))
(point-max))))
(set-visited-file-modtime)
(set-buffer-modified-p nil)
(vlfi-update-buffer-name))
(defun vlfi-move-to-batch (start &optional minimal)
"Move to batch determined by START.
Adjust according to file start/end and show `vlfi-batch-size' bytes.
When given MINIMAL flag, skip non important operations."
(or (verify-visited-file-modtime (current-buffer))
(setq vlfi-file-size (vlfi-get-file-size buffer-file-name)))
(setq vlfi-start-pos (max 0 start)
vlfi-end-pos (min (+ vlfi-start-pos vlfi-batch-size)
vlfi-file-size))
(if (= vlfi-file-size vlfi-end-pos) ; re-check file size
(setq vlfi-start-pos (max 0 (- vlfi-end-pos vlfi-batch-size))))
(let ((inhibit-read-only t)
(pos (position-bytes (point))))
(erase-buffer)
(insert-file-contents buffer-file-name nil
vlfi-start-pos vlfi-end-pos)
(goto-char (or (byte-to-position (+ pos (vlfi-adjust-chunk)))
(point-max))))
(set-buffer-modified-p nil)
(set-visited-file-modtime)
(or minimal(vlfi-update-buffer-name)))
(defun vlfi-move-to-chunk (start end &optional minimal)
"Move to chunk determined by START END.
When given MINIMAL flag, skip non important operations."
(or (verify-visited-file-modtime (current-buffer))
(setq vlfi-file-size (vlfi-get-file-size buffer-file-name)))
(setq vlfi-start-pos (max 0 start)
vlfi-end-pos (min end vlfi-file-size))
(let ((inhibit-read-only t)
(pos (position-bytes (point))))
(erase-buffer)
(insert-file-contents buffer-file-name nil
vlfi-start-pos vlfi-end-pos)
(goto-char (or (byte-to-position (+ pos (vlfi-adjust-chunk)))
(point-max))))
(set-buffer-modified-p nil)
(set-visited-file-modtime)
(or minimal (vlfi-update-buffer-name)))
(defun vlfi-adjust-chunk ()
"Adjust chunk beginning until content can be properly decoded.
Return number of bytes moved back for this to happen."
(let ((shift 0))
(while (and (not (zerop vlfi-start-pos))
(< shift 3)
(/= (- vlfi-end-pos vlfi-start-pos)
(length (encode-coding-region
(point-min) (point-max)
buffer-file-coding-system t))))
(setq shift (1+ shift)
vlfi-start-pos (1- vlfi-start-pos))
(let ((inhibit-read-only t))
(erase-buffer)
(insert-file-contents buffer-file-name nil
vlfi-start-pos vlfi-end-pos)))
(set-buffer-modified-p nil)
shift))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; search
(defun vlfi-re-search (regexp count backward)
"Search for REGEXP COUNT number of times forward or BACKWARD."
(let* ((match-chunk-start vlfi-start-pos)
(match-chunk-end vlfi-end-pos)
(match-start-pos (+ vlfi-start-pos (position-bytes (point))))
(match-end-pos match-start-pos)
(to-find count)
(search-reporter (make-progress-reporter
(concat "Searching for " regexp "...")
(if backward
(- vlfi-file-size vlfi-end-pos)
vlfi-start-pos)
vlfi-file-size))
(batch-step (/ vlfi-batch-size 8))) ; amount of chunk overlap
(unwind-protect
(catch 'end-of-file
(while (not (zerop to-find))
(cond ((re-search-forward regexp nil t)
(setq to-find (if (= match-start-pos
(match-beginning 0))
to-find
(1- to-find))
match-start-pos (match-beginning 0)
match-end-pos (match-end 0)
match-chunk-start vlfi-start-pos
match-chunk-end vlfi-end-pos)
(if (and (< vlfi-batch-size match-start-pos)
(> (- vlfi-end-pos vlfi-start-pos)
vlfi-batch-size))
(setq match-chunk-start
(+ match-chunk-start vlfi-batch-size)
match-start-pos (- match-start-pos
vlfi-batch-size)
match-end-pos (- match-end-pos
vlfi-batch-size))))
((= vlfi-end-pos vlfi-file-size)
(throw 'end-of-file nil))
(t (if initial-chunk
(progn (setq initial-chunk nil)
(vlfi-next-batch -1))
(vlfi-move-to-chunk (+ vlfi-start-pos
vlfi-batch-size)
(+ vlfi-end-pos
vlfi-batch-size)))
(goto-char (if (< vlfi-start-pos match-chunk-end)
match-start-pos
(point-min)))
(goto-char match-start-pos)
(progress-reporter-update search-reporter
vlfi-end-pos))))
(if backward
(while (not (zerop to-find))
(cond ((re-search-backward regexp nil t)
(setq to-find (1- to-find)
match-chunk-start vlfi-start-pos
match-chunk-end vlfi-end-pos
match-start-pos (+ vlfi-start-pos
(position-bytes
(match-beginning 0)))
match-end-pos (+ vlfi-start-pos
(position-bytes
(match-end 0)))))
((zerop vlfi-start-pos)
(throw 'end-of-file nil))
(t (let ((batch-move (- vlfi-start-pos
(- vlfi-batch-size
batch-step))))
(vlfi-move-to-batch
(if (< match-start-pos batch-move)
(- match-start-pos vlfi-batch-size)
batch-move) t))
(goto-char (if (< match-start-pos
vlfi-end-pos)
(or (byte-to-position
(- match-start-pos
vlfi-start-pos))
(point-max))
(point-max)))
(progress-reporter-update
search-reporter (- vlfi-file-size
vlfi-start-pos)))))
(while (not (zerop to-find))
(cond ((re-search-forward regexp nil t)
(setq to-find (1- to-find)
match-chunk-start vlfi-start-pos
match-chunk-end vlfi-end-pos
match-start-pos (+ vlfi-start-pos
(position-bytes
(match-beginning 0)))
match-end-pos (+ vlfi-start-pos
(position-bytes
(match-end 0)))))
((= vlfi-end-pos vlfi-file-size)
(throw 'end-of-file nil))
(t (let ((batch-move (- vlfi-end-pos batch-step)))
(vlfi-move-to-batch
(if (< batch-move match-end-pos)
match-end-pos
batch-move) t))
(goto-char (if (< vlfi-start-pos match-end-pos)
(or (byte-to-position
(- match-end-pos
vlfi-start-pos))
(point-max))
(point-min)))
(progress-reporter-update search-reporter
vlfi-end-pos)))))
(progress-reporter-done search-reporter))
(vlfi-end-search match-chunk-start match-chunk-end
match-end-pos count to-find))))
(if backward
(vlfi-goto-match match-chunk-start match-chunk-end
match-end-pos match-start-pos
count to-find)
(vlfi-goto-match match-chunk-start match-chunk-end
match-start-pos match-end-pos
count to-find)))))
(defun vlfi-re-search-backward (regexp count)
"Search backward for REGEXP prefix COUNT number of times."
(defun vlfi-goto-match (match-chunk-start match-chunk-end
match-pos-start
match-pos-end
count to-find)
"Move to MATCH-CHUNK-START MATCH-CHUNK-END surrounding \
MATCH-POS-START and MATCH-POS-END.
According to COUNT and left TO-FIND, show if search has been
successful. Return nil if nothing found."
(if (= count to-find)
(progn (vlfi-move-to-chunk match-chunk-start match-chunk-end)
(goto-char (or (byte-to-position (- match-pos-start
vlfi-start-pos))
(point-max)))
(message "Not found")
nil)
(let ((success (zerop to-find)))
(if success
(vlfi-update-buffer-name)
(vlfi-move-to-chunk match-chunk-start match-chunk-end))
(let* ((match-end (or (byte-to-position (- match-pos-end
vlfi-start-pos))
(point-max)))
(overlay (make-overlay (byte-to-position
(- match-pos-start
vlfi-start-pos))
match-end)))
(overlay-put overlay 'face 'region)
(unless success
(goto-char match-end)
(message "Moved to the %d match which is last"
(- count to-find)))
(sit-for 0.1)
(delete-overlay overlay)
t))))
(defun vlfi-re-search-forward (regexp count)
"Search forward for REGEXP prefix COUNT number of times.
Search is performed chunk by chunk in `vlfi-batch-size' memory."
(interactive (list (read-regexp "Search whole file"
(if regexp-history
(car regexp-history))
'regexp-history)
(car regexp-history)))
(or current-prefix-arg 1)))
(let ((match-chunk-start vlfi-start-pos)
(match-chunk-end vlfi-end-pos)
(match-start-pos (point))
(match-end-pos (point))
(to-find count)
(search-reporter (make-progress-reporter
(concat "Searching for " regexp)
(- vlfi-file-size vlfi-end-pos)
vlfi-file-size))
(initial-chunk t))
(unwind-protect
(catch 'start-of-file
(while (not (zerop to-find))
(cond ((re-search-backward regexp nil t)
(setq to-find (if (= match-end-pos
(match-end 0))
to-find
(1- to-find))
match-start-pos (match-beginning 0)
match-end-pos (match-end 0)
match-chunk-start vlfi-start-pos
match-chunk-end vlfi-end-pos)
(if (and (< match-end-pos vlfi-batch-size)
(> (- vlfi-end-pos vlfi-start-pos)
vlfi-batch-size))
(setq match-chunk-end
(- match-chunk-end
vlfi-batch-size))))
((zerop vlfi-start-pos)
(throw 'start-of-file nil))
(t (if initial-chunk
(progn (setq initial-chunk nil)
(vlfi-prev-batch -1))
(vlfi-move-to-chunk (- vlfi-start-pos
vlfi-batch-size)
(- vlfi-end-pos
vlfi-batch-size)))
(goto-char (if (< match-chunk-start vlfi-end-pos)
match-end-pos
(point-max)))
(setq last-chunk-match nil)
(progress-reporter-update search-reporter
(- vlfi-file-size
vlfi-start-pos)))))
(progress-reporter-done search-reporter))
(vlfi-end-search match-chunk-start match-chunk-end
match-start-pos count to-find))))
(vlfi-re-search regexp count nil))
(defun vlfi-end-search (match-chunk-start match-chunk-end
match-pos count to-find)
"Move to chunk determined by MATCH-CHUNK-START and MATCH-CHUNK-END.
Go to MATCH-POS and according to COUNT and left TO-FIND show if search
has been successful. Return nil if nothing found."
(vlfi-move-to-chunk match-chunk-start match-chunk-end)
(goto-char match-pos)
(cond ((zerop to-find) t)
((< to-find count)
(message "Moved to the %d match which is last found"
(- count to-find))
t)
(t (message "Not found")
nil)))
(defun vlfi-re-search-backward (regexp count)
"Search backward for REGEXP prefix COUNT number of times.
Search is performed chunk by chunk in `vlfi-batch-size' memory."
(interactive (list (read-regexp "Search whole file backward"
(if regexp-history
(car regexp-history)))
(or current-prefix-arg 1)))
(vlfi-re-search regexp count t))
(defun vlfi-goto-line (n)
"Go to line N."
(interactive "nGo to line: ")
(let ((start-pos vlfi-start-pos)
(end-pos vlfi-end-pos)
(pos (point))
(success nil))
(unwind-protect
(progn (vlfi-beginning-of-file)
(goto-char (point-min))
(setq success (vlfi-re-search-forward "[\n\C-m]"
(1- n))))
(unless success
(vlfi-move-to-chunk start-pos end-pos)
(goto-char pos)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; editing
(defvar vlfi-edit-mode-map
(let ((map (make-sparse-keymap)))
(set-keymap-parent map text-mode-map)
(define-key map "\C-c\C-c" 'vlfi-write)
(define-key map "\C-c\C-q" 'vlfi-discard-edit)
map)
"Keymap for command `vlfi-edit-mode'.")
(define-derived-mode vlfi-edit-mode vlfi-mode "VLFI[edit]"
"Major mode for editing large file chunks."
(setq buffer-read-only nil)
(buffer-enable-undo)
(message (substitute-command-keys
"Editing: Type \\[vlfi-write] to write chunk \
or \\[vlfi-discard-edit] to discard changes.")))
(defun vlfi-discard-edit ()
"Discard edit and refresh chunk from file."
(interactive)
(vlfi-move-to-chunk vlfi-start-pos vlfi-end-pos)
(vlfi-mode)
(message "Switched to VLFI mode."))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; saving
(defun vlfi-write ()
"Write current chunk to file. Always return true to disable save.
If changing size of chunk shift remaining file content."
(interactive)
(when (and (buffer-modified-p)
(or (verify-visited-file-modtime (current-buffer))
(y-or-n-p "File has changed since visited or saved. \
Save anyway? ")))
(let ((pos (point))
(size-change (- vlfi-end-pos vlfi-start-pos
(length (encode-coding-region
(point-min) (point-max)
buffer-file-coding-system t)))))
(cond ((zerop size-change)
(write-region nil nil buffer-file-name vlfi-start-pos t))
((< 0 size-change)
(vlfi-file-shift-back size-change))
(t (vlfi-file-shift-forward (- size-change))))
(vlfi-move-to-chunk vlfi-start-pos vlfi-end-pos)
(goto-char pos))
(vlfi-mode)
t))
(defun vlfi-file-shift-back (size-change)
"Shift file contents SIZE-CHANGE bytes back."
(write-region nil nil buffer-file-name vlfi-start-pos t)
(buffer-disable-undo)
(let ((read-start-pos vlfi-end-pos)
(coding-system-for-write 'no-conversion)
(reporter (make-progress-reporter "Adjusting file content..."
vlfi-end-pos
vlfi-file-size)))
(while (vlfi-shift-batch read-start-pos (- read-start-pos
size-change))
(setq read-start-pos (+ read-start-pos vlfi-batch-size))
(progress-reporter-update reporter read-start-pos))
;; pad end with space
(erase-buffer)
(insert-char 32 size-change)
(write-region nil nil buffer-file-name (- vlfi-file-size
size-change) t)
(progress-reporter-done reporter)))
(defun vlfi-shift-batch (read-pos write-pos)
"Read `vlfi-batch-size' bytes from READ-POS and write them \
back at WRITE-POS. Return nil if EOF is reached, t otherwise."
(erase-buffer)
(or (verify-visited-file-modtime (current-buffer))
(setq vlfi-file-size (vlfi-get-file-size buffer-file-name)))
(let ((read-end (+ read-pos vlfi-batch-size)))
(insert-file-contents-literally buffer-file-name nil
read-pos
(min vlfi-file-size read-end))
(write-region nil nil buffer-file-name write-pos 0)
(< read-end vlfi-file-size)))
(defun vlfi-file-shift-forward (size-change)
"Shift file contents SIZE-CHANGE bytes forward.
Done by saving content up front and then writing previous batch."
(buffer-disable-undo)
(let ((size (+ vlfi-batch-size size-change))
(read-pos vlfi-end-pos)
(write-pos vlfi-start-pos)
(reporter (make-progress-reporter "Adjusting file content..."
vlfi-start-pos
vlfi-file-size)))
(when (vlfi-shift-batches size read-pos write-pos t)
(setq write-pos (+ read-pos size-change)
read-pos (+ read-pos size))
(progress-reporter-update reporter write-pos)
(let ((coding-system-for-write 'no-conversion))
(while (vlfi-shift-batches size read-pos write-pos nil)
(setq write-pos (+ read-pos size-change)
read-pos (+ read-pos size))
(progress-reporter-update reporter write-pos))))
(progress-reporter-done reporter)))
(defun vlfi-shift-batches (size read-pos write-pos hide-read)
"Append SIZE bytes of file starting at READ-POS.
Then write initial buffer content to file at WRITE-POS.
If HIDE-READ is non nil, temporarily hide literal read content.
Return nil if EOF is reached, t otherwise."
(or (verify-visited-file-modtime (current-buffer))
(setq vlfi-file-size (vlfi-get-file-size buffer-file-name)))
(let ((read-more (< read-pos vlfi-file-size))
(start-write-pos (point-min))
(end-write-pos (point-max)))
(when read-more
(goto-char end-write-pos)
(insert-file-contents-literally buffer-file-name nil read-pos
(min vlfi-file-size (+ read-pos
size))))
;; write
(if hide-read ; hide literal region if user has to choose encoding
(narrow-to-region start-write-pos end-write-pos))
(write-region start-write-pos end-write-pos
buffer-file-name write-pos 0)
(delete-region start-write-pos end-write-pos)
(if hide-read (widen))
read-more))
(provide 'vlfi)