1 ;;; vlf-base.el --- VLF primitive operations -*- lexical-binding: t -*-
3 ;; Copyright (C) 2014 Free Software Foundation, Inc.
5 ;; Keywords: large files, chunk
6 ;; Author: Andrey Kotlarski <m00naticus@gmail.com>
7 ;; URL: https://github.com/m00natic/vlfi
9 ;; This file is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 3, or (at your option)
14 ;; This file is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;; GNU General Public License for more details.
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs; see the file COPYING. If not, write to
21 ;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 ;; Boston, MA 02111-1307, USA.
25 ;; This package provides basic chunk operations for VLF,
26 ;; most notable being the `vlf-move-to-chunk' function.
31 "View Large Files in Emacs."
35 (defcustom vlf-batch-size 1024
36 "Defines how large each batch of file data is (in bytes)."
39 (put 'vlf-batch-size 'permanent-local t)
41 ;;; Keep track of file position.
42 (defvar vlf-start-pos 0
43 "Absolute position of the visible chunk start.")
44 (make-variable-buffer-local 'vlf-start-pos)
45 (put 'vlf-start-pos 'permanent-local t)
47 (defvar vlf-end-pos 0 "Absolute position of the visible chunk end.")
48 (make-variable-buffer-local 'vlf-end-pos)
49 (put 'vlf-end-pos 'permanent-local t)
51 (defvar vlf-file-size 0 "Total size of presented file.")
52 (make-variable-buffer-local 'vlf-file-size)
53 (put 'vlf-file-size 'permanent-local t)
55 (defconst vlf-sample-size 24
56 "Minimal number of bytes that can be properly decoded.")
58 (defun vlf-get-file-size (file)
59 "Get size in bytes of FILE."
60 (or (nth 7 (file-attributes file)) 0))
62 (defun vlf-verify-size (&optional update-visited-time)
63 "Update file size information if necessary and visited file time.
64 If non-nil, UPDATE-VISITED-TIME."
65 (unless (verify-visited-file-modtime (current-buffer))
66 (setq vlf-file-size (vlf-get-file-size buffer-file-truename))
67 (if update-visited-time
68 (set-visited-file-modtime))))
70 (unless (fboundp 'file-size-human-readable)
71 (defun file-size-human-readable (file-size)
72 "Print FILE-SIZE in MB."
73 (format "%.3fMB" (/ file-size 1048576.0))))
75 (defun vlf-update-buffer-name ()
76 "Update the current buffer name."
77 (rename-buffer (format "%s(%d/%d)[%s]"
78 (file-name-nondirectory buffer-file-name)
79 (/ vlf-end-pos vlf-batch-size)
80 (/ vlf-file-size vlf-batch-size)
81 (file-size-human-readable vlf-batch-size))
84 (defmacro vlf-with-undo-disabled (&rest body)
85 "Execute BODY with temporarily disabled undo."
86 `(let ((undo-list buffer-undo-list))
87 (setq buffer-undo-list t)
88 (unwind-protect (progn ,@body)
89 (setq buffer-undo-list undo-list))))
91 (defun vlf-move-to-chunk (start end &optional minimal)
92 "Move to chunk enclosed by START END bytes.
93 When given MINIMAL flag, skip non important operations.
94 If same as current chunk is requested, do nothing.
95 Return number of bytes moved back for proper decoding and number of
96 bytes added to the end."
97 (unless (and (= start vlf-start-pos)
100 (let ((shifts (vlf-move-to-chunk-1 start end)))
101 (and shifts (not minimal)
102 (vlf-update-buffer-name))
105 (defun vlf-move-to-chunk-1 (start end)
106 "Move to chunk enclosed by START END keeping as much edits if any.
107 Return number of bytes moved back for proper decoding and number of
108 bytes added to the end."
110 (let* ((modified (buffer-modified-p))
111 (start (max 0 start))
112 (end (min end vlf-file-size))
113 (edit-end (if modified
115 (length (encode-coding-region
116 (point-min) (point-max)
117 buffer-file-coding-system t)))
120 ((or (< edit-end start) (< end vlf-start-pos)
121 (not (verify-visited-file-modtime (current-buffer))))
122 (when (or (not modified)
123 (y-or-n-p "Chunk modified, are you sure? ")) ;full chunk renewal
124 (set-buffer-modified-p nil)
125 (vlf-move-to-chunk-2 start end)))
126 ((and (= start vlf-start-pos) (= end edit-end))
127 (or modified (vlf-move-to-chunk-2 start end)))
128 ((or (and (<= start vlf-start-pos) (<= edit-end end))
130 (y-or-n-p "Chunk modified, are you sure? "))
131 (let ((shift-start 0)
133 (let ((pos (+ (position-bytes (point)) vlf-start-pos))
134 (inhibit-read-only t))
135 (cond ((< end edit-end)
136 (let* ((del-pos (1+ (or (byte-to-position
137 (- end vlf-start-pos))
139 (del-len (length (encode-coding-region
141 buffer-file-coding-system
143 (setq end (- (if (zerop vlf-end-pos)
147 (vlf-with-undo-disabled
148 (delete-region del-pos (point-max)))))
150 (vlf-with-undo-disabled
151 (setq shift-end (cdr (vlf-insert-file-contents
153 (/= start vlf-end-pos) t
155 (cond ((< vlf-start-pos start)
156 (let* ((del-pos (1+ (byte-to-position
157 (- start vlf-start-pos))))
158 (del-len (length (encode-coding-region
160 buffer-file-coding-system
162 (setq start (+ vlf-start-pos del-len))
163 (vlf-with-undo-disabled
164 (delete-region (point-min) del-pos))
165 (vlf-shift-undo-list (- (point-min) del-pos))))
166 ((< start vlf-start-pos)
167 (let ((edit-end-pos (point-max)))
168 (vlf-with-undo-disabled
169 (setq shift-start (car (vlf-insert-file-contents
170 start vlf-start-pos t
171 (/= end vlf-start-pos)
173 (goto-char (point-min))
174 (insert (delete-and-extract-region
175 edit-end-pos (point-max))))
176 (vlf-shift-undo-list (- (point-max) edit-end-pos)))))
177 (setq start (- start shift-start))
178 (goto-char (or (byte-to-position (- pos start))
179 (byte-to-position (- pos vlf-start-pos))
181 (setq vlf-start-pos start
182 vlf-end-pos (+ end shift-end)))
183 (set-buffer-modified-p modified)
184 (set-visited-file-modtime)
185 (cons shift-start shift-end))))))
187 (defun vlf-move-to-chunk-2 (start end)
188 "Unconditionally move to chunk enclosed by START END bytes.
189 Return number of bytes moved back for proper decoding and number of
190 bytes added to the end."
192 (setq vlf-start-pos (max 0 start)
193 vlf-end-pos (min end vlf-file-size))
195 (let ((inhibit-read-only t)
196 (pos (position-bytes (point))))
197 (vlf-with-undo-disabled
199 (setq shifts (vlf-insert-file-contents vlf-start-pos
201 vlf-start-pos (- vlf-start-pos (car shifts))
202 vlf-end-pos (+ vlf-end-pos (cdr shifts)))
203 (goto-char (or (byte-to-position (+ pos (car shifts)))
205 (set-buffer-modified-p nil)
206 (setq buffer-undo-list nil)
209 (defun vlf-insert-file-contents (start end adjust-start adjust-end
211 "Adjust chunk at absolute START to END till content can be\
212 properly decoded. ADJUST-START determines if trying to prepend bytes\
213 to the beginning, ADJUST-END - append to the end.
214 Use buffer POSITION as start if given.
215 Return number of bytes moved back for proper decoding and number of
216 bytes added to the end."
217 (setq adjust-start (and adjust-start (not (zerop start)))
218 adjust-end (and adjust-end (< end vlf-file-size))
219 position (or position (point-min)))
221 (let ((shift-start 0)
223 (safe-end (if adjust-end
224 (min vlf-file-size (+ end 4))
227 (setq shift-start (vlf-adjust-start start safe-end position
229 start (- start shift-start))
230 (vlf-insert-file-contents-safe start safe-end position))
232 (setq shift-end (vlf-adjust-end start end position)))
233 (cons shift-start shift-end)))
235 (defun vlf-adjust-start (start end position adjust-end)
236 "Adjust chunk beginning at absolute START to END till content can\
237 be properly decoded. Use buffer POSITION as start.
238 ADJUST-END is non-nil if end would be adjusted later.
239 Return number of bytes moved back for proper decoding."
240 (let* ((safe-start (max 0 (- start 4)))
241 (sample-end (min end (+ safe-start vlf-sample-size)))
242 (chunk-size (- sample-end safe-start))
243 (strict (or (= sample-end vlf-file-size)
244 (and (not adjust-end) (= sample-end end))))
246 (while (and (progn (vlf-insert-file-contents-safe
247 safe-start sample-end position)
248 (not (zerop safe-start)))
250 (let ((diff (- chunk-size
252 (encode-coding-region
254 buffer-file-coding-system t)))))
257 (or (< diff -3) (< 0 diff)))))
258 (setq shift (1+ shift)
259 safe-start (1- safe-start)
260 chunk-size (1+ chunk-size))
261 (delete-region position (point-max)))
262 (let ((cut-pos position)
264 (while (< safe-start start)
265 (setq cut-len (length (encode-coding-region
267 buffer-file-coding-system t))
269 safe-start (+ safe-start cut-len)))
270 (if (< start safe-start)
271 (setq safe-start (- safe-start cut-len)
272 cut-pos (1- cut-pos)))
273 (if (= sample-end end)
274 (delete-region position cut-pos)
275 (delete-region position (point-max))
276 (vlf-insert-file-contents-safe safe-start end position)))
277 (- start safe-start)))
279 (defun vlf-adjust-end (start end position)
280 "Adjust chunk end at absolute START to END starting at POSITION.
281 Remove characters from the end until length is closest to expected.
282 Return number of bytes added over expected."
283 (let ((expected-size (- end start))
284 (current-size (length (encode-coding-region
286 buffer-file-coding-system t)))
287 (cut-point (point-max))
289 (while (< expected-size current-size)
290 (setq cut-len (length (encode-coding-region
291 (1- cut-point) cut-point
292 buffer-file-coding-system t))
293 cut-point (1- cut-point)
294 current-size (- current-size cut-len)))
295 (if (< current-size expected-size)
296 (setq cut-point (1+ cut-point)
297 current-size (+ current-size cut-len)))
298 (delete-region cut-point (point-max))
299 (- current-size expected-size)))
301 (defun vlf-insert-file-contents-safe (start end position)
302 "Extract decoded file bytes START to END at POSITION."
303 (let ((coding buffer-file-coding-system))
304 (insert-file-contents-literally buffer-file-name nil start end)
305 (let ((coding-system-for-read coding))
306 (decode-coding-inserted-region position (point-max)
307 buffer-file-name nil start end)))
308 (setq buffer-file-coding-system last-coding-system-used))
310 (defun vlf-shift-undo-list (n)
311 "Shift undo list element regions by N."
312 (or (eq buffer-undo-list t)
313 (setq buffer-undo-list
315 (let ((min (point-min))
318 (dolist (el buffer-undo-list undo-list)
322 ((numberp el) (let ((pos (+ el n)))
324 (throw 'end undo-list)
326 (t (let ((head (car el)))
327 (cond ((numberp head)
328 (let ((beg (+ head n)))
330 (throw 'end undo-list)
331 (cons beg (+ (cdr el) n)))))
333 (let* ((pos (cdr el))
335 (new (+ (abs pos) n)))
337 (throw 'end undo-list)
338 (cons head (if positive
342 (let ((beg (+ (nth 3 el) n)))
344 (throw 'end undo-list)
354 ((and (eq head 'apply)
356 (let ((beg (+ (nth 2 el) n)))
358 (throw 'end undo-list)
368 (cdr (last el))))))))))
374 ;;; vlf-base.el ends here