;;; vlf-search.el --- Search functionality for VLF -*- lexical-binding: t -*- ;; Copyright (C) 2014 Free Software Foundation, Inc. ;; Keywords: large files, search ;; Author: Andrey Kotlarski ;; 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 3, 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 search utilities for dealing with large files ;; in constant memory. ;;; Code: (require 'vlf) (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")) (run-hook-with-args 'vlf-before-batch-functions 'search) (let* ((tramp-verbose (if (boundp 'tramp-verbose) (min tramp-verbose 2))) (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) (font-lock font-lock-mode) (reporter (make-progress-reporter (concat "Searching for " regexp "...") (if backward (- vlf-file-size vlf-end-pos) vlf-start-pos) vlf-file-size))) (font-lock-mode 0) (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 font-lock (font-lock-mode 1)) (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)) (run-hook-with-args 'vlf-after-batch-functions 'search))))) (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: ")))) (run-hook-with-args 'vlf-before-batch-functions 'goto-line) (vlf-verify-size) (let ((tramp-verbose (if (boundp 'tramp-verbose) (min tramp-verbose 2))) (start-pos vlf-start-pos) (end-pos vlf-end-pos) (pos (point)) (font-lock font-lock-mode) (success nil)) (font-lock-mode 0) (unwind-protect (if (< 0 n) (let ((start 0) (end (min vlf-batch-size vlf-file-size)) (reporter (make-progress-reporter (concat "Searching for line " (number-to-string n) "...") 0 vlf-file-size)) (inhibit-read-only t)) (setq n (1- n)) (vlf-with-undo-disabled (while (and (< (- end start) n) (< n (- vlf-file-size start))) (erase-buffer) (insert-file-contents-literally buffer-file-name nil start end) (goto-char (point-min)) (while (re-search-forward "[\n\C-m]" nil t) (setq n (1- n))) (vlf-verify-size) (setq start end end (min vlf-file-size (+ start vlf-batch-size))) (progress-reporter-update reporter start)) (when (< n (- vlf-file-size end)) (vlf-move-to-chunk-2 start end) (goto-char (point-min)) (setq success (vlf-re-search "[\n\C-m]" n nil 0))))) (let ((start (max 0 (- vlf-file-size vlf-batch-size))) (end vlf-file-size) (reporter (make-progress-reporter (concat "Searching for line -" (number-to-string n) "...") 0 vlf-file-size)) (inhibit-read-only t)) (setq n (- n)) (vlf-with-undo-disabled (while (and (< (- end start) n) (< n end)) (erase-buffer) (insert-file-contents-literally buffer-file-name nil start end) (goto-char (point-max)) (while (re-search-backward "[\n\C-m]" nil t) (setq n (1- n))) (setq end start start (max 0 (- end vlf-batch-size))) (progress-reporter-update reporter (- vlf-file-size end))) (when (< n end) (vlf-move-to-chunk-2 start end) (goto-char (point-max)) (setq success (vlf-re-search "[\n\C-m]" n t 0)))))) (if font-lock (font-lock-mode 1)) (unless success (vlf-with-undo-disabled (vlf-move-to-chunk-2 start-pos end-pos)) (vlf-update-buffer-name) (goto-char pos) (message "Unable to find line")) (run-hook-with-args 'vlf-after-batch-functions 'goto-line)))) (provide 'vlf-search) ;;; vlf-search.el ends here