1
0
mirror of https://github.com/m00natic/vlfi.git synced 2025-11-06 18:11:37 +00:00

115 Commits
0.5 ... 1.1

Author SHA1 Message Date
Andrey Kotlarski
371c560f4b Bump version and remove warning - save is now reliable. 2013-12-11 01:11:13 +02:00
Andrey Kotlarski
a3901b8f1a Fix deletion when moving to partially overlapping chunk and enable more
intelligent behaviour in cases of overlapping and modifications.
2013-12-09 01:55:41 +02:00
Andrey Kotlarski
f4ee23c07f Fix chunk end adjustment and save for current and older Emacsen. 2013-12-08 21:11:08 +02:00
Andrey Kotlarski
959bbc7661 Disable undo in cases of partial chunk move. 2013-12-08 17:22:23 +02:00
Andrey Kotlarski
0080991fa9 Fix chunk end adjustment and save for trunk Emacs. 2013-12-08 17:21:34 +02:00
Andrey Kotlarski
46e39a0fd8 Version 1.0. 2013-12-07 20:20:41 +02:00
Andrey Kotlarski
51d95ec0a3 Add command to display batch starting from point. 2013-12-07 20:18:07 +02:00
Andrey Kotlarski
d6c722376c Delete obsolete vlfi.el. 2013-12-07 19:50:19 +02:00
Andrey Kotlarski
a42247cac4 Automatically scroll to adjacent batch when start or end of chunk is
visible.
2013-12-04 15:11:04 +02:00
Andrey Kotlarski
3e8098af61 Rename vlf-discard-edit -> vlf-refresh. 2013-12-04 14:44:11 +02:00
Andrey Kotlarski
28646fbfee Reduce scope of vlf-with-undo-disabled usages. 2013-12-04 14:40:07 +02:00
Andrey Kotlarski
177c680288 Revert to showing batch size in buffer name instead of the mode-line. 2013-12-04 14:00:24 +02:00
Andrey Kotlarski
2ac3e7d577 Fix prematurely ending search/occur not to ask for modified buffer
confirmation.
2013-12-04 13:59:08 +02:00
Andrey Kotlarski
931ca52688 Stylistic refinements. 2013-12-04 00:20:02 +02:00
Andrey Kotlarski
42e581da61 Add command to unconditionally open fresh VLF buffer to visit occur
match.
2013-12-04 00:15:31 +02:00
Andrey Kotlarski
a65f3a431d Check for unsaved changes before search query and don't enable undo if
it was previously disabled.
2013-12-04 00:03:29 +02:00
Andrey Kotlarski
f34986a9b8 Update README. 2013-12-03 02:54:33 +02:00
Andrey Kotlarski
452b7eb42d Ensure there are no modifications when executing searches. 2013-12-03 02:37:13 +02:00
Andrey Kotlarski
2dba838015 In case original VLF buffer has been killed, try to find existing VLF
buffer for the same file when visiting occur results.
2013-12-03 02:04:27 +02:00
Andrey Kotlarski
cb47e19128 Use temporary buffer for occur in case of modifications. 2013-12-03 01:50:03 +02:00
Andrey Kotlarski
efae918a83 Turn vlf into minor mode. 2013-12-02 02:08:24 +02:00
Andrey Kotlarski
7a141091c8 Fix search for GNU Emacs 23 (no assert). 2013-08-26 12:29:39 +03:00
Andrey Kotlarski
8ab1c6a4f2 Disable undo and mark buffer as not modified when invoking search or
indexing.
2013-08-26 12:03:18 +03:00
Andrey Kotlarski
ae2237d7ea Minor adjustments:
- fix batch position formatting
- unconditionally update chunk on revert
- do nothing if same chunk is requested in `vlf-move-to-chunk'
2013-08-26 00:39:51 +03:00
Andrey Kotlarski
9b22b74f21 Add gitignore. 2013-08-25 23:01:55 +03:00
Andrey Kotlarski
025399a644 Merge branch 'master' into keep-edit
Conflicts:
	README.org
	vlfi.el
2013-08-25 19:17:04 +03:00
Andrey Kotlarski
bfcbfd33cb Make error message more descriptive. 2013-08-25 18:23:06 +03:00
Andrey Kotlarski
bc68eed5a2 Add dummy vlfi.el that urges move to the vlf package. 2013-08-25 18:14:53 +03:00
Andrey Kotlarski
17c66122d9 Rename anything vlfi -> vlf. 2013-08-25 17:57:43 +03:00
Andrey Kotlarski
4c631bcbe5 Rename vlfi.el to vlf.el. 2013-08-25 17:51:34 +03:00
Andrey Kotlarski
e5457690fb Fix adjusting of end chunk bytes. 2013-08-25 17:48:28 +03:00
Andrey Kotlarski
d1b34dcefb Add autoload cookie for `file-size-human-readable' not to break GNU
Emacs 23 opening of large files.
2013-08-13 12:39:55 +03:00
Andrey Kotlarski
737c1e9979 Remove encode size bookkeeping. 2013-08-09 02:00:34 +03:00
Andrey Kotlarski
2d71996d9e Add end character shift when adjusting chunk. Apply partial chunk
update when buffer is not modified too.
2013-08-09 01:59:32 +03:00
Andrey Kotlarski
0ecb40a5ee Keep as much editing as possible when moving to intersecting chunk. 2013-08-08 12:43:00 +03:00
Andrey Kotlarski
02a37c4192 Merge branch 'master' into chunk-opt2 2013-08-07 01:19:26 +03:00
Andrey Kotlarski
7cb047407d Add forgotten vlfi-encode-size local variable creation. 2013-08-06 16:41:16 +03:00
Andrey Kotlarski
34dfbd202d Use set-visited-file-name and abstract getting current file size and
updating visited time.
2013-08-06 16:23:04 +03:00
Andrey Kotlarski
ce9441fc83 Update info as VLFI is merging back to VLF. Bump version. 2013-08-06 02:14:39 +03:00
Andrey Kotlarski
563d9ca423 Fix vlfi-revert to revert on user confirmation. 2013-08-06 02:13:01 +03:00
Andrey Kotlarski
298d8f59d9 Keep track of current batch encoded size and allow fluctuation when
searching for proper decoding.
2013-08-06 02:11:21 +03:00
Andrey Kotlarski
8ed919209a Review fixes:
- move put statements to top level
- add write-file-functions hook locally
- vlfi-if-file-too-large becomes around advice
- minor style changes
2013-08-06 02:07:36 +03:00
Andrey Kotlarski
c1aedbc307 Update README: VLFI is now development version of VLF. 2013-08-06 02:02:02 +03:00
Andrey Kotlarski
a0b8d5f67a Merge pull request #3 from danlamanna/master
Alters vlfi to respect the default-directory variable when opening
files.
2013-07-25 01:17:35 -07:00
Dan LaManna
616d676330 Alters vlfi to respect the default-directory variable when opening
files.
2013-07-24 21:49:14 -04:00
Andrey Kotlarski
b943008eca Bump version and update README. 2013-06-09 02:34:52 +03:00
Andrey Kotlarski
4589c25d96 Return to current position after occur ending with quit. 2013-05-07 15:43:52 +03:00
Andrey Kotlarski
3c2fb6b93e Merge branch 'master' into chunk-opt2 2013-05-05 15:57:16 +03:00
Andrey Kotlarski
c827c3e186 Prevent standard save procedure invocation in case user postpones
saving.
2013-05-04 23:53:17 +03:00
Andrey Kotlarski
36d2ed4d09 Update README. 2013-05-02 14:25:39 +03:00
Andrey Kotlarski
de1948ed36 Merge branch 'master' into chunk-opt2 2013-05-02 14:19:50 +03:00
Andrey Kotlarski
ae775f88f9 Extend vlfi-goto-line to count lines from the end with negative
argument.
2013-05-02 14:18:41 +03:00
Andrey Kotlarski
1ac1eece40 Turn vlfi-get-file-size to function. 2013-05-02 12:04:12 +03:00
Andrey Kotlarski
bbda9abc7d Merge branch 'master' into chunk-opt2
Conflicts:
	vlfi.el
2013-05-01 02:25:39 +03:00
Andrey Kotlarski
67732485d8 Optimize a bit goto line. 2013-05-01 02:18:37 +03:00
Andrey Kotlarski
3d652fe71d Minor documentation fixes. 2013-05-01 02:02:28 +03:00
Andrey Kotlarski
7bfe665524 More secure chunk decode adjustment and minor fixes. 2013-05-01 01:51:36 +03:00
Andrey Kotlarski
ca13beac24 Try utf-8 and if not successful, auto detect when adjusting chunk for
proper decoding.
2013-04-28 01:12:07 +03:00
Andrey Kotlarski
d2bd47c2b4 More attempts to optimize decoding. 2013-04-24 01:32:50 +03:00
Andrey Kotlarski
57eb4c270a Initial attempt at optimizing chunk access. 2013-04-24 01:32:50 +03:00
Colin Marquardt
28255a2aa2 Correctly print MB (for older emacsen). 2013-04-23 19:15:43 +03:00
Andrey Kotlarski
fbe081417c Fix occur indexing not to skip last chunk. 2013-04-23 00:53:54 +03:00
Andrey Kotlarski
fd9c258fc8 Use permanent buffer local write hook and update README. 2013-04-17 14:46:50 +03:00
Andrey Kotlarski
a63ea7a5d0 Grammar fixes. 2013-04-17 13:19:37 +03:00
Andrey Kotlarski
fbc3a37ce5 Add tip for ability to change major mode and more section hierarchy. 2013-04-17 13:11:40 +03:00
Andrey Kotlarski
eaf85e5cff Add key-binding to vlfi-mode-map in vlfi-edit-mode-map. 2013-04-17 13:00:37 +03:00
Andrey Kotlarski
e4d886a3e3 Add check if VLFI buffer has been modified before occur jumping to new
chunk.
2013-04-17 12:49:29 +03:00
Andrey Kotlarski
b8cc34422e Add more detailed usage details. 2013-04-17 12:33:04 +03:00
Andrey Kotlarski
a5fec57e32 Mostly documentation and commentary added. 2013-04-17 11:55:02 +03:00
Andrey Kotlarski
70719b0917 Add vlfi-occur-show command for showing match but still staying in
occur buffer.
2013-04-17 11:53:49 +03:00
Andrey Kotlarski
54927afb8f Update README and bump version. 2013-04-16 15:56:10 +03:00
Andrey Kotlarski
f14ef6ce9e Usability improvements to vlfi-occur. 2013-04-16 15:51:30 +03:00
Andrey Kotlarski
1dcd12288b Use overlays for number indicators. 2013-04-16 15:38:36 +03:00
Andrey Kotlarski
641ff4b961 First feature complete version of vlfi-occur. 2013-04-16 14:44:34 +03:00
Andrey Kotlarski
681d3d94fc Add vlfi-occur-mode. 2013-04-16 12:25:06 +03:00
Andrey Kotlarski
ba439de0f6 Initial generation of vlfi-occur results. 2013-04-16 02:16:03 +03:00
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
4 changed files with 1091 additions and 419 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*.elc
*~

View File

@@ -1,13 +1,99 @@
* View Large File Improved
* View Large Files
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:
Emacs minor mode that allows viewing, editing and searching large
files in batches. 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
- chunk editing
- options to jump to end or beginning of file
This is development version of the GNU ELPA [[http://elpa.gnu.org/packages/vlf][vlf.el]] package. Here's
what it does in a nutshell:
- 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)
- occur like indexing
- 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
- vlfi is added as an option when opening large files
- newly added content is acknowledged if file has changed size
meanwhile
- automatic scrolling of batches
- VLF is added as an option when opening large files
GNU Emacs 23 and 24 are supported.
* Overview and tips
M-x vlf PATH-TO-FILE
** Unicode
Emacs' Unicode support is leveraged so you'll not see bare bytes but
characters decoded as if file is normally opened. This holds for
editing, search and indexing.
** 32-bit GNU Emacs
Regular Emacs integers are used, so if you use 32-bit Emacs without
bignum support and have really huge file (with size beyond the maximum
integer value), VLF will probably not quite work.
** Memory control
*vlf-batch-size* bounds the memory used for all operations.
* Detail usage
** Control batch size
*C-c C-v +* and *C-c C-v -* control current batch size by factors
of 2.
You can also set by hand local variable *vlf-batch-size* and then
refresh with *C-c C-v g*.
** Move around
*C-c C-v PgUp* and *C-c C-v PgDn* move batch by batch. With positive
prefix argument they move prefix number of batches. With negative -
append prefix number of batches.
*C-c C-v n* displays batch starting from current point.
*C-c C-v [* and *C-c C-v ]* take you to the beginning and end of file
respectively.
*C-c C-v j* jumps to given chunk. To see where you are in file and
how many chunks there are (using the current batch size), look at the
parenthesized part of the buffer name, batch size is also indicated at
the end.
** Search whole file
*C-c C-v s* and *C-c C-v r* search forward and backward respectively
over the whole file. This is done batch by batch so if you have
really huge file - you'd better set somewhat bigger batch size
beforehand.
** Occur over whole file
*C-c C-v o* builds index for given regular expression just like
*M-x occur*. It does this batch by batch over the whole file. Note
that even if you prematurely stop it with *C-g*, it will still show
index of what's found so far.
** Jump to line
*C-c C-v l* jumps to given line in file. This is done by searching
from the beginning, so again the bigger current batch size, the
quicker. With negative argument, lines are counted from the end of
file.
** Reload
*C-c C-v g* discards modifications (if such) and reloads chunk.
** Edit and save
If editing doesn't change size of the chunk, only this chunk is saved.
Otherwise the remaining part of the file is adjusted batch by batch,
so again you'd better have bigger current batch size.

994
vlf.el Normal file
View File

@@ -0,0 +1,994 @@
;;; vlf.el --- View Large Files -*- lexical-binding: t -*-
;; Copyright (C) 2006, 2012, 2013 Free Software Foundation, Inc.
;; Version: 1.1
;; Keywords: large files, utilities
;; Maintainer: Andrey Kotlarski <m00naticus@gmail.com>
;; Authors: 2006 Mathias Dahl <mathias.dahl@gmail.com>
;; 2012 Sam Steingold <sds@gnu.org>
;; 2013 Andrey Kotlarski <m00naticus@gmail.com>
;; URL: https://github.com/m00natic/vlfi
;; This file is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.
;; This file is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to
;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.
;;; Commentary:
;; This package provides the M-x vlf command, which visits part of a
;; large file without loading the entire file.
;; The buffer uses VLF mode, which defines several commands for
;; moving around, searching and editing selected part of file.
;; This package was inspired by a snippet posted by Kevin Rodgers,
;; showing how to use `insert-file-contents' to extract part of a
;; file.
;;; Code:
(defgroup vlf nil
"View Large Files in Emacs."
:prefix "vlf-"
:group 'files)
(defcustom vlf-batch-size 1024
"Defines how large each batch of file data is (in bytes)."
:type 'integer
:group 'vlf)
(put 'vlf-batch-size 'permanent-local t)
;;; Keep track of file position.
(defvar vlf-start-pos 0
"Absolute position of the visible chunk start.")
(put 'vlf-start-pos 'permanent-local t)
(defvar vlf-end-pos 0 "Absolute position of the visible chunk end.")
(put 'vlf-end-pos 'permanent-local t)
(defvar vlf-file-size 0 "Total size of presented file.")
(put 'vlf-file-size 'permanent-local t)
(defvar vlf-mode-map
(let ((map-prefix (make-sparse-keymap))
(map (make-sparse-keymap)))
(define-key map [next] 'vlf-next-batch)
(define-key map [prior] 'vlf-prev-batch)
(define-key map "n" 'vlf-next-batch-from-point)
(define-key map "+" 'vlf-change-batch-size)
(define-key map "-"
(lambda () "Decrease vlf batch size by factor of 2."
(interactive)
(vlf-change-batch-size t)))
(define-key map "s" 'vlf-re-search-forward)
(define-key map "r" 'vlf-re-search-backward)
(define-key map "o" 'vlf-occur)
(define-key map "[" 'vlf-beginning-of-file)
(define-key map "]" 'vlf-end-of-file)
(define-key map "j" 'vlf-jump-to-chunk)
(define-key map "l" 'vlf-goto-line)
(define-key map "g" 'vlf-refresh)
(define-key map-prefix "\C-c\C-v" map)
map-prefix)
"Keymap for `vlf-mode'.")
(define-minor-mode vlf-mode
"Mode to browse large files in."
:lighter " VLF"
:group 'vlf
:keymap vlf-mode-map
(if vlf-mode
(progn
(set (make-local-variable 'require-final-newline) nil)
(add-hook 'write-file-functions 'vlf-write nil t)
(set (make-local-variable 'revert-buffer-function)
'vlf-revert)
(make-local-variable 'vlf-batch-size)
(set (make-local-variable 'vlf-start-pos) -1)
(make-local-variable 'vlf-end-pos)
(set (make-local-variable 'vlf-file-size)
(vlf-get-file-size buffer-file-name))
(let* ((pos (position-bytes (point)))
(start (* (/ pos vlf-batch-size) vlf-batch-size)))
(goto-char (byte-to-position (- pos start)))
(vlf-move-to-batch start)))
(kill-local-variable 'revert-buffer-function)
(when (or (not large-file-warning-threshold)
(< vlf-file-size large-file-warning-threshold)
(y-or-n-p (format "Load whole file (%s)? "
(file-size-human-readable
vlf-file-size))))
(kill-local-variable 'require-final-newline)
(remove-hook 'write-file-functions 'vlf-write t)
(let ((pos (+ vlf-start-pos (position-bytes (point)))))
(vlf-with-undo-disabled
(insert-file-contents buffer-file-name t nil nil t))
(goto-char (byte-to-position pos)))
(rename-buffer (file-name-nondirectory buffer-file-name) t))))
;;;###autoload
(defun vlf (file)
"View Large FILE in batches.
You can customize number of bytes displayed by customizing
`vlf-batch-size'."
(interactive "fFile to open: ")
(with-current-buffer (generate-new-buffer "*vlf*")
(set-visited-file-name file)
(set-buffer-modified-p nil)
(vlf-mode 1)
(switch-to-buffer (current-buffer))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; integration with other packages
;;;###autoload
(defun dired-vlf ()
"In Dired, visit the file on this line in VLF mode."
(interactive)
(vlf (dired-get-file-for-visit)))
;;;###autoload
(eval-after-load "dired"
'(define-key dired-mode-map "V" 'dired-vlf))
;;;###autoload
(defadvice abort-if-file-too-large (around vlf-if-file-too-large
(size op-type
&optional filename)
compile activate)
"If file SIZE larger than `large-file-warning-threshold', \
allow user to view file with `vlf', open it normally, or abort.
OP-TYPE specifies the file operation being performed over FILENAME."
(and large-file-warning-threshold size
(> size large-file-warning-threshold)
(let ((char nil))
(while (not (memq (setq char
(read-event
(propertize
(format
"File %s is large (%s): \
%s normally (o), %s with vlf (v) or abort (a)"
(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))
(vlf filename)
(error ""))
((memq char '(?a ?A))
(error "Aborted"))))))
;; scroll auto batching
(defadvice scroll-up (around vlf-scroll-up
activate compile)
"Slide to next batch if at end of buffer in `vlf-mode'."
(if (and vlf-mode (pos-visible-in-window-p (point-max)))
(progn (vlf-next-batch 1)
(goto-char (point-min)))
ad-do-it))
(defadvice scroll-down (around vlf-scroll-down
activate compile)
"Slide to previous batch if at beginning of buffer in `vlf-mode'."
(if (and vlf-mode (pos-visible-in-window-p (point-min)))
(progn (vlf-prev-batch 1)
(goto-char (point-max)))
ad-do-it))
;; non-recent Emacs
;;;###autoload
(unless (fboundp 'file-size-human-readable)
(defun file-size-human-readable (file-size)
"Print FILE-SIZE in MB."
(format "%.1fMB" (/ file-size 1048576.0))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; utilities
(defun vlf-change-batch-size (decrease)
"Change the buffer-local value of `vlf-batch-size'.
Normally, the value is doubled;
with the prefix argument DECREASE it is halved."
(interactive "P")
(setq vlf-batch-size (if decrease
(/ vlf-batch-size 2)
(* vlf-batch-size 2)))
(vlf-move-to-batch vlf-start-pos))
(defun vlf-update-buffer-name ()
"Update the current buffer name."
(rename-buffer (format "%s(%d/%d)[%s]"
(file-name-nondirectory buffer-file-name)
(/ vlf-end-pos vlf-batch-size)
(/ vlf-file-size vlf-batch-size)
(file-size-human-readable vlf-batch-size))
t))
(defun vlf-get-file-size (file)
"Get size in bytes of FILE."
(nth 7 (file-attributes file)))
(defun vlf-verify-size ()
"Update file size information if necessary and visited file time."
(unless (verify-visited-file-modtime (current-buffer))
(setq vlf-file-size (vlf-get-file-size buffer-file-name))
(set-visited-file-modtime)))
(defun vlf-insert-file (&optional from-end)
"Insert first chunk of current file contents in current buffer.
With FROM-END prefix, start from the back."
(let ((start 0)
(end vlf-batch-size))
(if from-end
(setq start (- vlf-file-size vlf-batch-size)
end vlf-file-size)
(setq end (min vlf-batch-size vlf-file-size)))
(vlf-move-to-chunk start end)))
(defun vlf-beginning-of-file ()
"Jump to beginning of file content."
(interactive)
(vlf-insert-file))
(defun vlf-end-of-file ()
"Jump to end of file content."
(interactive)
(vlf-insert-file t))
(defun vlf-revert (&optional _ignore-auto noconfirm)
"Revert current chunk. Ignore _IGNORE-AUTO.
Ask for confirmation if NOCONFIRM is nil."
(if (or noconfirm
(yes-or-no-p (format "Revert buffer from file %s? "
buffer-file-name)))
(vlf-move-to-chunk-2 vlf-start-pos vlf-end-pos)))
(defun vlf-jump-to-chunk (n)
"Go to to chunk N."
(interactive "nGoto to chunk: ")
(vlf-move-to-batch (* (1- n) vlf-batch-size)))
(defmacro vlf-with-undo-disabled (&rest body)
"Execute BODY with temporarily disabled undo."
`(let ((undo-enabled (not (eq buffer-undo-list t))))
(if undo-enabled
(buffer-disable-undo))
(unwind-protect (progn ,@body)
(if undo-enabled
(buffer-enable-undo)))))
(defun vlf-no-modifications ()
"Ensure there are no buffer modifications."
(if (buffer-modified-p)
(error "Save or discard your changes first")
t))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; batch movement
(defun vlf-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")
(vlf-verify-size)
(let* ((end (min (+ vlf-end-pos (* vlf-batch-size
(abs append)))
vlf-file-size))
(start (if (< append 0)
vlf-start-pos
(- end vlf-batch-size))))
(vlf-move-to-chunk start end)))
(defun vlf-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 vlf-start-pos)
(error "Already at BOF"))
(let* ((start (max 0 (- vlf-start-pos (* vlf-batch-size
(abs prepend)))))
(end (if (< prepend 0)
vlf-end-pos
(+ start vlf-batch-size))))
(vlf-move-to-chunk start end)))
(defun vlf-move-to-batch (start &optional minimal)
"Move to batch determined by START.
Adjust according to file start/end and show `vlf-batch-size' bytes.
When given MINIMAL flag, skip non important operations."
(vlf-verify-size)
(let* ((start (max 0 start))
(end (min (+ start vlf-batch-size) vlf-file-size)))
(if (= vlf-file-size end) ; re-adjust start
(setq start (max 0 (- end vlf-batch-size))))
(vlf-move-to-chunk start end minimal)))
(defun vlf-next-batch-from-point ()
"Display batch of file data starting from current point."
(interactive)
(vlf-move-to-batch (+ vlf-start-pos (position-bytes (point)) -1))
(goto-char (point-min)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; primitive chunk operations
(defun vlf-move-to-chunk (start end &optional minimal)
"Move to chunk determined by START END.
When given MINIMAL flag, skip non important operations.
If same as current chunk is requested, do nothing."
(unless (and (= start vlf-start-pos)
(= end vlf-end-pos))
(vlf-verify-size)
(if (vlf-move-to-chunk-1 start end)
(or minimal (vlf-update-buffer-name)))))
(defun vlf-move-to-chunk-1 (start end)
"Move to chunk determined by START END keeping as much edits if any.
Return t if move hasn't been canceled."
(let ((modified (buffer-modified-p))
(start (max 0 start))
(end (min end vlf-file-size))
(edit-end (+ (position-bytes (point-max)) vlf-start-pos)))
(cond
((and (= start vlf-start-pos) (= end edit-end))
(unless modified
(vlf-move-to-chunk-2 start end)
t))
((or (<= edit-end start) (<= end vlf-start-pos))
(when (or (not modified)
(y-or-n-p "Chunk modified, are you sure? ")) ;full chunk renewal
(set-buffer-modified-p nil)
(vlf-move-to-chunk-2 start end)
t))
((or (and (<= start vlf-start-pos) (<= edit-end end))
(not modified)
(y-or-n-p "Chunk modified, are you sure? "))
(let ((pos (+ (position-bytes (point)) vlf-start-pos))
(shift-start 0)
(shift-end 0)
(inhibit-read-only t))
(cond ((< end edit-end)
(let* ((del-pos (1+ (byte-to-position
(- end vlf-start-pos))))
(del-len (length (encode-coding-region
del-pos (point-max)
buffer-file-coding-system
t))))
(setq end (- vlf-end-pos del-len))
(vlf-with-undo-disabled
(delete-region del-pos (point-max)))))
((< edit-end end)
(let ((edit-end-pos (point-max)))
(goto-char edit-end-pos)
(vlf-with-undo-disabled
(insert-file-contents buffer-file-name nil
vlf-end-pos end)
(setq shift-end (cdr (vlf-adjust-chunk
vlf-end-pos end nil t
edit-end-pos)))))))
(cond ((< vlf-start-pos start)
(let* ((del-pos (1+ (byte-to-position
(- start vlf-start-pos))))
(del-len (length (encode-coding-region
(point-min) del-pos
buffer-file-coding-system
t))))
(setq start (+ vlf-start-pos del-len))
(vlf-with-undo-disabled
(delete-region (point-min) del-pos))))
((< start vlf-start-pos)
(let ((edit-end-pos (point-max)))
(goto-char edit-end-pos)
(vlf-with-undo-disabled
(insert-file-contents buffer-file-name nil
start vlf-start-pos)
(setq shift-start (car
(vlf-adjust-chunk start
vlf-start-pos
t nil
edit-end-pos)))
(goto-char (point-min))
(insert (delete-and-extract-region edit-end-pos
(point-max)))))))
(setq vlf-start-pos (- start shift-start)
vlf-end-pos (+ end shift-end))
(goto-char (or (byte-to-position (- pos vlf-start-pos))
(point-max))))
(set-buffer-modified-p modified)
t))))
(defun vlf-move-to-chunk-2 (start end)
"Unconditionally move to chunk determined by START END."
(setq vlf-start-pos (max 0 start)
vlf-end-pos (min end vlf-file-size))
(let ((inhibit-read-only t)
(pos (position-bytes (point))))
(vlf-with-undo-disabled
(erase-buffer)
(insert-file-contents buffer-file-name nil
vlf-start-pos vlf-end-pos)
(let ((shifts (vlf-adjust-chunk vlf-start-pos vlf-end-pos t
t)))
(setq vlf-start-pos (- vlf-start-pos (car shifts))
vlf-end-pos (+ vlf-end-pos (cdr shifts)))
(goto-char (or (byte-to-position (+ pos (car shifts)))
(point-max))))))
(set-buffer-modified-p nil)
(set-visited-file-modtime))
(defun vlf-adjust-chunk (start end &optional adjust-start adjust-end
position)
"Adjust chunk at absolute START to END till content can be\
properly decoded. ADJUST-START determines if trying to prepend bytes\
to the beginning, ADJUST-END - append to the end.
Use buffer POSITION as start if given.
Return number of bytes moved back for proper decoding and number of
bytes added to the end."
(let ((shift-start 0)
(shift-end 0))
(if adjust-start
(let ((position (or position (point-min)))
(chunk-size (- end start)))
(while (and (not (zerop start))
(< shift-start 4)
(< 4 (abs (- chunk-size
(length (encode-coding-region
position (point-max)
buffer-file-coding-system
t))))))
(setq shift-start (1+ shift-start)
start (1- start)
chunk-size (1+ chunk-size))
(delete-region position (point-max))
(goto-char position)
(insert-file-contents buffer-file-name nil start end))))
(if adjust-end
(cond ((vlf-partial-decode-shown-p) ;remove raw bytes from end
(goto-char (point-max))
(while (eq (char-charset (preceding-char)) 'eight-bit)
(setq shift-end (1- shift-end))
(delete-char -1)))
((< end vlf-file-size) ;add bytes until new character is displayed
(let ((position (or position (point-min)))
(expected-size (buffer-size)))
(while (and (progn
(setq shift-end (1+ shift-end)
end (1+ end))
(delete-region position (point-max))
(goto-char position)
(insert-file-contents buffer-file-name
nil start end)
(< end vlf-file-size))
(= expected-size (buffer-size))))))))
(cons shift-start shift-end)))
(defun vlf-partial-decode-shown-p ()
"Determine if partial decode codes are displayed.
This seems to be the case with GNU/Emacs before 24.4."
(cond ((< emacs-major-version 24) t)
((< 24 emacs-major-version) nil)
(t ;; TODO: use (< emacs-minor-version 4) after 24.4 release
(string-lessp emacs-version "24.3.5"))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; search
(defun vlf-re-search (regexp count backward batch-step)
"Search for REGEXP COUNT number of times forward or BACKWARD.
BATCH-STEP is amount of overlap between successive chunks."
(if (<= count 0)
(error "Count must be positive"))
(let* ((case-fold-search t)
(match-chunk-start vlf-start-pos)
(match-chunk-end vlf-end-pos)
(match-start-pos (+ vlf-start-pos (position-bytes (point))))
(match-end-pos match-start-pos)
(to-find count)
(reporter (make-progress-reporter
(concat "Searching for " regexp "...")
(if backward
(- vlf-file-size vlf-end-pos)
vlf-start-pos)
vlf-file-size)))
(vlf-with-undo-disabled
(unwind-protect
(catch 'end-of-file
(if backward
(while (not (zerop to-find))
(cond ((re-search-backward regexp nil t)
(setq to-find (1- to-find)
match-chunk-start vlf-start-pos
match-chunk-end vlf-end-pos
match-start-pos (+ vlf-start-pos
(position-bytes
(match-beginning 0)))
match-end-pos (+ vlf-start-pos
(position-bytes
(match-end 0)))))
((zerop vlf-start-pos)
(throw 'end-of-file nil))
(t (let ((batch-move (- vlf-start-pos
(- vlf-batch-size
batch-step))))
(vlf-move-to-batch
(if (< match-start-pos batch-move)
(- match-start-pos vlf-batch-size)
batch-move) t))
(goto-char (if (< match-start-pos
vlf-end-pos)
(or (byte-to-position
(- match-start-pos
vlf-start-pos))
(point-max))
(point-max)))
(progress-reporter-update
reporter (- vlf-file-size
vlf-start-pos)))))
(while (not (zerop to-find))
(cond ((re-search-forward regexp nil t)
(setq to-find (1- to-find)
match-chunk-start vlf-start-pos
match-chunk-end vlf-end-pos
match-start-pos (+ vlf-start-pos
(position-bytes
(match-beginning 0)))
match-end-pos (+ vlf-start-pos
(position-bytes
(match-end 0)))))
((= vlf-end-pos vlf-file-size)
(throw 'end-of-file nil))
(t (let ((batch-move (- vlf-end-pos batch-step)))
(vlf-move-to-batch
(if (< batch-move match-end-pos)
match-end-pos
batch-move) t))
(goto-char (if (< vlf-start-pos match-end-pos)
(or (byte-to-position
(- match-end-pos
vlf-start-pos))
(point-min))
(point-min)))
(progress-reporter-update reporter
vlf-end-pos)))))
(progress-reporter-done reporter))
(set-buffer-modified-p nil)
(if backward
(vlf-goto-match match-chunk-start match-chunk-end
match-end-pos match-start-pos
count to-find)
(vlf-goto-match match-chunk-start match-chunk-end
match-start-pos match-end-pos
count to-find))))))
(defun vlf-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 (vlf-move-to-chunk match-chunk-start match-chunk-end)
(goto-char (or (byte-to-position (- match-pos-start
vlf-start-pos))
(point-max)))
(message "Not found")
nil)
(let ((success (zerop to-find)))
(if success
(vlf-update-buffer-name)
(vlf-move-to-chunk match-chunk-start match-chunk-end))
(let* ((match-end (or (byte-to-position (- match-pos-end
vlf-start-pos))
(point-max)))
(overlay (make-overlay (byte-to-position
(- match-pos-start
vlf-start-pos))
match-end)))
(overlay-put overlay 'face 'match)
(unless success
(goto-char match-end)
(message "Moved to the %d match which is last"
(- count to-find)))
(unwind-protect (sit-for 3)
(delete-overlay overlay))
t))))
(defun vlf-re-search-forward (regexp count)
"Search forward for REGEXP prefix COUNT number of times.
Search is performed chunk by chunk in `vlf-batch-size' memory."
(interactive (if (vlf-no-modifications)
(list (read-regexp "Search whole file"
(if regexp-history
(car regexp-history)))
(or current-prefix-arg 1))))
(vlf-re-search regexp count nil (/ vlf-batch-size 8)))
(defun vlf-re-search-backward (regexp count)
"Search backward for REGEXP prefix COUNT number of times.
Search is performed chunk by chunk in `vlf-batch-size' memory."
(interactive (if (vlf-no-modifications)
(list (read-regexp "Search whole file backward"
(if regexp-history
(car regexp-history)))
(or current-prefix-arg 1))))
(vlf-re-search regexp count t (/ vlf-batch-size 8)))
(defun vlf-goto-line (n)
"Go to line N. If N is negative, count from the end of file."
(interactive (if (vlf-no-modifications)
(list (read-number "Go to line: "))))
(let ((start-pos vlf-start-pos)
(end-pos vlf-end-pos)
(pos (point))
(success nil))
(unwind-protect
(if (< 0 n)
(progn (vlf-beginning-of-file)
(goto-char (point-min))
(setq success (vlf-re-search "[\n\C-m]" (1- n)
nil 0)))
(vlf-end-of-file)
(goto-char (point-max))
(setq success (vlf-re-search "[\n\C-m]" (- n) t 0)))
(if success
(message "Onto line %s" n)
(vlf-move-to-chunk start-pos end-pos)
(goto-char pos)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; occur
(defvar vlf-occur-mode-map
(let ((map (make-sparse-keymap)))
(define-key map "n" 'vlf-occur-next-match)
(define-key map "p" 'vlf-occur-prev-match)
(define-key map "\C-m" 'vlf-occur-visit)
(define-key map "\M-\r" 'vlf-occur-visit-new-buffer)
(define-key map [mouse-1] 'vlf-occur-visit)
(define-key map "o" 'vlf-occur-show)
map)
"Keymap for command `vlf-occur-mode'.")
(define-derived-mode vlf-occur-mode special-mode "VLF[occur]"
"Major mode for showing occur matches of VLF opened files.")
(defun vlf-occur-next-match ()
"Move cursor to next match."
(interactive)
(if (eq (get-char-property (point) 'face) 'match)
(goto-char (next-single-property-change (point) 'face)))
(goto-char (or (text-property-any (point) (point-max) 'face 'match)
(text-property-any (point-min) (point)
'face 'match))))
(defun vlf-occur-prev-match ()
"Move cursor to previous match."
(interactive)
(if (eq (get-char-property (point) 'face) 'match)
(goto-char (previous-single-property-change (point) 'face)))
(while (not (eq (get-char-property (point) 'face) 'match))
(goto-char (or (previous-single-property-change (point) 'face)
(point-max)))))
(defun vlf-occur-show (&optional event)
"Visit current `vlf-occur' link in a vlf buffer but stay in the \
occur buffer. If original VLF buffer has been killed,
open new VLF session each time.
EVENT may hold details of the invocation."
(interactive (list last-nonmenu-event))
(let ((occur-buffer (if event
(window-buffer (posn-window
(event-end event)))
(current-buffer))))
(vlf-occur-visit event)
(pop-to-buffer occur-buffer)))
(defun vlf-occur-visit-new-buffer ()
"Visit `vlf-occur' link in new vlf buffer."
(interactive)
(let ((current-prefix-arg t))
(vlf-occur-visit)))
(defun vlf-occur-visit (&optional event)
"Visit current `vlf-occur' link in a vlf buffer.
With prefix argument or if original VLF buffer has been killed,
open new VLF session.
EVENT may hold details of the invocation."
(interactive (list last-nonmenu-event))
(when event
(set-buffer (window-buffer (posn-window (event-end event))))
(goto-char (posn-point (event-end event))))
(let* ((pos (point))
(pos-relative (- pos (line-beginning-position) 1))
(file (get-char-property pos 'file)))
(if file
(let ((chunk-start (get-char-property pos 'chunk-start))
(chunk-end (get-char-property pos 'chunk-end))
(vlf-buffer (get-char-property pos 'buffer))
(occur-buffer (current-buffer))
(match-pos (+ (get-char-property pos 'line-pos)
pos-relative)))
(cond (current-prefix-arg
(setq vlf-buffer (vlf file))
(switch-to-buffer occur-buffer))
((not (buffer-live-p vlf-buffer))
(or (catch 'found
(dolist (buf (buffer-list))
(set-buffer buf)
(and vlf-mode (equal file buffer-file-name)
(setq vlf-buffer buf)
(throw 'found t))))
(setq vlf-buffer (vlf file)))
(switch-to-buffer occur-buffer)))
(pop-to-buffer vlf-buffer)
(vlf-move-to-chunk chunk-start chunk-end)
(goto-char match-pos)))))
(defun vlf-occur (regexp)
"Make whole file occur style index for REGEXP.
Prematurely ending indexing will still show what's found so far."
(interactive (list (read-regexp "List lines matching regexp"
(if regexp-history
(car regexp-history)))))
(if (buffer-modified-p) ;use temporary buffer not to interfere with modifications
(let ((vlf-buffer (current-buffer))
(file buffer-file-name)
(batch-size vlf-batch-size))
(with-temp-buffer
(setq buffer-file-name file)
(set-buffer-modified-p nil)
(set (make-local-variable 'vlf-batch-size) batch-size)
(vlf-mode 1)
(goto-char (point-min))
(vlf-with-undo-disabled
(vlf-build-occur regexp vlf-buffer))))
(let ((start-pos vlf-start-pos)
(end-pos vlf-end-pos)
(pos (point)))
(vlf-beginning-of-file)
(goto-char (point-min))
(vlf-with-undo-disabled
(unwind-protect (vlf-build-occur regexp (current-buffer))
(vlf-move-to-chunk start-pos end-pos)
(goto-char pos))))))
(defun vlf-build-occur (regexp vlf-buffer)
"Build occur style index for REGEXP over VLF-BUFFER."
(let ((case-fold-search t)
(line 1)
(last-match-line 0)
(last-line-pos (point-min))
(file buffer-file-name)
(total-matches 0)
(match-end-pos (+ vlf-start-pos (position-bytes (point))))
(occur-buffer (generate-new-buffer
(concat "*VLF-occur " (file-name-nondirectory
buffer-file-name)
"*")))
(line-regexp (concat "\\(?5:[\n\C-m]\\)\\|\\(?10:"
regexp "\\)"))
(batch-step (/ vlf-batch-size 8))
(end-of-file nil)
(reporter (make-progress-reporter
(concat "Building index for " regexp "...")
vlf-start-pos vlf-file-size)))
(unwind-protect
(progn
(while (not end-of-file)
(if (re-search-forward line-regexp nil t)
(progn
(setq match-end-pos (+ vlf-start-pos
(position-bytes
(match-end 0))))
(if (match-string 5)
(setq line (1+ line) ; line detected
last-line-pos (point))
(let* ((chunk-start vlf-start-pos)
(chunk-end vlf-end-pos)
(line-pos (line-beginning-position))
(line-text (buffer-substring
line-pos (line-end-position))))
(with-current-buffer occur-buffer
(unless (= line last-match-line) ;new match line
(insert "\n:") ; insert line number
(let* ((overlay-pos (1- (point)))
(overlay (make-overlay
overlay-pos
(1+ overlay-pos))))
(overlay-put overlay 'before-string
(propertize
(number-to-string line)
'face 'shadow)))
(insert (propertize line-text ; insert line
'file file
'buffer vlf-buffer
'chunk-start chunk-start
'chunk-end chunk-end
'mouse-face '(highlight)
'line-pos line-pos
'help-echo
(format "Move to line %d"
line))))
(setq last-match-line line
total-matches (1+ total-matches))
(let ((line-start (1+
(line-beginning-position)))
(match-pos (match-beginning 10)))
(add-text-properties ; mark match
(+ line-start match-pos (- last-line-pos))
(+ line-start (match-end 10)
(- last-line-pos))
(list 'face 'match
'help-echo
(format "Move to match %d"
total-matches))))))))
(setq end-of-file (= vlf-end-pos vlf-file-size))
(unless end-of-file
(let ((batch-move (- vlf-end-pos batch-step)))
(vlf-move-to-batch (if (< batch-move match-end-pos)
match-end-pos
batch-move) t))
(goto-char (if (< vlf-start-pos match-end-pos)
(or (byte-to-position (- match-end-pos
vlf-start-pos))
(point-min))
(point-min)))
(setq last-match-line 0
last-line-pos (line-beginning-position))
(progress-reporter-update reporter vlf-end-pos))))
(progress-reporter-done reporter))
(set-buffer-modified-p nil)
(if (zerop total-matches)
(progn (with-current-buffer occur-buffer
(set-buffer-modified-p nil))
(kill-buffer occur-buffer)
(message "No matches for \"%s\"" regexp))
(with-current-buffer occur-buffer
(goto-char (point-min))
(insert (propertize
(format "%d matches in %d lines for \"%s\" \
in file: %s" total-matches line regexp file)
'face 'underline))
(set-buffer-modified-p nil)
(forward-char 2)
(vlf-occur-mode))
(display-buffer occur-buffer)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; editing
(defun vlf-refresh ()
"Discard edit and refresh chunk from file."
(interactive)
(set-buffer-modified-p nil)
(vlf-move-to-chunk-2 vlf-start-pos vlf-end-pos))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; saving
(defun vlf-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 (- vlf-end-pos vlf-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 vlf-start-pos t))
((< 0 size-change)
(vlf-file-shift-back size-change))
(t (vlf-file-shift-forward (- size-change))))
(vlf-move-to-chunk-2 vlf-start-pos vlf-end-pos)
(goto-char pos)))
t)
(defun vlf-file-shift-back (size-change)
"Shift file contents SIZE-CHANGE bytes back."
(write-region nil nil buffer-file-name vlf-start-pos t)
(let ((read-start-pos vlf-end-pos)
(coding-system-for-write 'no-conversion)
(reporter (make-progress-reporter "Adjusting file content..."
vlf-end-pos
vlf-file-size)))
(vlf-with-undo-disabled
(while (vlf-shift-batch read-start-pos (- read-start-pos
size-change))
(setq read-start-pos (+ read-start-pos vlf-batch-size))
(progress-reporter-update reporter read-start-pos))
;; pad end with space
(erase-buffer)
(vlf-verify-size)
(insert-char 32 size-change))
(write-region nil nil buffer-file-name (- vlf-file-size
size-change) t)
(progress-reporter-done reporter)))
(defun vlf-shift-batch (read-pos write-pos)
"Read `vlf-batch-size' bytes from READ-POS and write them \
back at WRITE-POS. Return nil if EOF is reached, t otherwise."
(erase-buffer)
(vlf-verify-size)
(let ((read-end (+ read-pos vlf-batch-size)))
(insert-file-contents-literally buffer-file-name nil
read-pos
(min vlf-file-size read-end))
(write-region nil nil buffer-file-name write-pos 0)
(< read-end vlf-file-size)))
(defun vlf-file-shift-forward (size-change)
"Shift file contents SIZE-CHANGE bytes forward.
Done by saving content up front and then writing previous batch."
(let ((read-size (max (/ vlf-batch-size 2) size-change))
(read-pos vlf-end-pos)
(write-pos vlf-start-pos)
(reporter (make-progress-reporter "Adjusting file content..."
vlf-start-pos
vlf-file-size)))
(vlf-with-undo-disabled
(when (vlf-shift-batches read-size read-pos write-pos t)
(setq write-pos (+ read-pos size-change)
read-pos (+ read-pos read-size))
(progress-reporter-update reporter write-pos)
(let ((coding-system-for-write 'no-conversion))
(while (vlf-shift-batches read-size read-pos write-pos nil)
(setq write-pos (+ read-pos size-change)
read-pos (+ read-pos read-size))
(progress-reporter-update reporter write-pos)))))
(progress-reporter-done reporter)))
(defun vlf-shift-batches (read-size read-pos write-pos hide-read)
"Append READ-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."
(vlf-verify-size)
(let ((read-more (< read-pos vlf-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 vlf-file-size
(+ read-pos read-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 'vlf)
;;; vlf.el ends here

410
vlfi.el
View File

@@ -1,410 +0,0 @@
;;; vlfi.el --- View Large Files Improved
;;; -*- lexical-bind: t -*-
;; Copyright (C) 2006, 2012, 2013 Free Software Foundation, Inc.
;; Version: 0.5
;; Keywords: large files, utilities
;; Authors: 2006 Mathias Dahl <mathias.dahl@gmail.com>
;; 2012 Sam Steingold <sds@gnu.org>
;; 2013 Andrey Kotlarski <m00naticus@gmail.com>
;; This file is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.
;; This file is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to
;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.
;;; Commentary:
;; 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 several commands for
;; moving around, searching and editing selected chunk of file.
;; This package is an improved fork of the vlf.el package.
;;; Code:
(defgroup vlfi nil
"View Large Files in Emacs."
:prefix "vlfi-"
:group 'files)
(defcustom vlfi-batch-size 1024
"Defines how large each batch of file data is (in bytes)."
:type 'integer
:group 'vlfi)
;;; 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 "+" '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 "e" 'vlfi-edit-mode)
map)
"Keymap for `vlfi-mode'.")
(define-derived-mode vlfi-mode special-mode "VLFI"
"Mode to browse large files in."
(setq buffer-read-only t)
(set-buffer-modified-p nil)
(buffer-disable-undo)
(make-local-variable 'vlfi-batch-size)
(put 'vlfi-batch-size 'permanent-local t)
(make-local-variable 'vlfi-start-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))
(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)[%.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-batch (start)
"Move to batch determined by START.
Adjust according to file start/end and show `vlfi-batch-size' bytes."
(setq vlfi-start-pos (max 0 start)
vlfi-end-pos (+ vlfi-start-pos vlfi-batch-size))
(if (< vlfi-file-size vlfi-end-pos) ; re-check file size
(setq vlfi-file-size
(nth 7 (file-attributes buffer-file-name))
vlfi-end-pos (min vlfi-end-pos vlfi-file-size)
vlfi-start-pos (max 0 (- vlfi-end-pos vlfi-batch-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-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))
;;;###autoload
(defun vlfi (file &optional from-end)
"View Large FILE. With FROM-END prefix, view from the back.
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")
(with-current-buffer (generate-new-buffer "*vlfi*")
(setq buffer-file-name file
vlfi-file-size (nth 7 (file-attributes file)))
(vlfi-insert-file file from-end)
(vlfi-mode)
(switch-to-buffer (current-buffer))))
;;;###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))
;;;###autoload
(eval-after-load "dired"
'(define-key dired-mode-map "V" 'dired-vlfi))
;;;###autoload
(defun vlfi-if-file-too-large (size op-type &optional filename)
"If file SIZE larger than `large-file-warning-threshold', \
allow user to view file with `vlfi', open it normally or abort.
OP-TYPE specifies the file operation being performed over FILENAME."
(and large-file-warning-threshold size
(> size large-file-warning-threshold)
(let ((char nil))
(while (not (memq (setq char
(read-event
(propertize
(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)
'face 'minibuffer-prompt)))
'(?o ?O ?v ?V ?a ?A))))
(cond ((memq char '(?o ?O)))
((memq char '(?v ?V))
(vlfi filename nil)
(error ""))
((memq char '(?a ?A))
(error "Aborted"))))))
;;; hijack `abort-if-file-too-large'
;;;###autoload
(fset 'abort-if-file-too-large 'vlfi-if-file-too-large)
;;; search
(defun vlfi-re-search (regexp count backward)
"Search for REGEXP COUNT number of times forward or BACKWARD."
(let* ((match-start-pos (+ vlfi-start-pos (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
(if backward
(while (not (zerop to-find))
(cond ((re-search-backward regexp nil t)
(setq to-find (1- to-find)
match-start-pos (+ vlfi-start-pos
(match-beginning 0))
match-end-pos (+ vlfi-start-pos
(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)))
(goto-char (if (< match-start-pos
vlfi-end-pos)
(- match-start-pos
vlfi-start-pos)
(point-max)))
(progress-reporter-update search-reporter
vlfi-start-pos))))
(while (not (zerop to-find))
(cond ((re-search-forward regexp nil t)
(setq to-find (1- to-find)
match-start-pos (+ vlfi-start-pos
(match-beginning 0))
match-end-pos (+ vlfi-start-pos
(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)))
(goto-char (if (< vlfi-start-pos match-end-pos)
(- match-end-pos vlfi-start-pos)
(point-min)))
(progress-reporter-update search-reporter
vlfi-end-pos)))))
(progress-reporter-done search-reporter))
(if backward
(vlfi-goto-match match-end-pos match-start-pos
count to-find)
(vlfi-goto-match match-start-pos match-end-pos
count to-find)))))
(defun vlfi-goto-match (match-pos-start match-pos-end count to-find)
"Move to chunk 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."
(let ((success (zerop to-find)))
(or success
(vlfi-move-to-batch (- match-pos-start
(/ vlfi-batch-size 2))))
(let* ((match-end (- match-pos-end vlfi-start-pos))
(overlay (make-overlay (- match-pos-start vlfi-start-pos)
match-end)))
(overlay-put overlay 'face 'region)
(or success (goto-char match-end))
(prog1 (cond (success t)
((< to-find count)
(message "Moved to the %d match which is last"
(- count to-find))
t)
(t (message "Not found")
nil))
(sit-for 0.1)
(delete-overlay overlay)))))
(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)))
(vlfi-re-search regexp count nil))
(defun vlfi-re-search-backward (regexp count)
"Search backward for REGEXP prefix COUNT number of times."
(interactive (list (read-regexp "Search whole file backward"
(if regexp-history
(car regexp-history))
'regexp-history)
(or current-prefix-arg 1)))
(vlfi-re-search regexp count t))
;;; 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-write ()
"Write current chunk to file. May overwrite existing content."
(interactive)
(when (or (= (buffer-size) (- vlfi-end-pos vlfi-start-pos))
(y-or-n-p "Changed size of original chunk. \
End of chunk will be garbled. Continue? "))
(write-region nil nil buffer-file-name vlfi-start-pos)
(vlfi-move-to-chunk vlfi-start-pos vlfi-end-pos)
(vlfi-mode)))
(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."))
(provide 'vlfi)
;;; vlfi.el ends here