2

I'm in a situation where I would like to look through a lot of text files as they are being created.

I would like to configure Emacs to do the following:

  1. Launch from a script with directory argument
  2. Open the oldest file in directory
  3. Have two buttons; "Next" switches the current buffer to the oldest younger file and "Prev" would switch to the youngest older file.

I'm imagining that it would look like the "undo/next" buttons from this picture of Proof General:

Picture of the GUI of Proof General

How do I go about doing this? Is it even possible?

  • 1
    One idea would be to use the existing facilities of dired-mode to either filter or search for desired filetypes and then just use the up/down command (for previous / next) assuming you have organized by date (either ascending or descending). – lawlist Jun 15 '18 at 23:27
  • .. and you can arrange that by setting dired-listing-switches to "-alt" (youngest at the top) or "-altr" (oldest at the top). – NickD Jun 16 '18 at 2:08
  • What do you mean by "Launch from a script with directory argument"? In my answer I assumed that you want to start Emacs with a new command line option which takes a directory name as argument - say emacs --oldest-in-dir ~/ - and Emacs shows the oldest regular file in that directory. But it seems that nobody else does interpret your first requirement in that way. – Tobias Jun 16 '18 at 15:35
  • It is not really a requirement. I just guessed that would be the natural way to implement it. That would probably work too. – firstname gklsodascb Jun 16 '18 at 19:32
0

You can copy-paste the following lisp code into your init-file.

It adds an option --oldest-in-dir with an existing non-empty directory as its obligatory value to the command line options of emacs.

If use that option emacs finds the oldest file in the given directory in the current buffer at startup.

Furthermore it switches on cycle-dir-global-mode which is also defined in the lisp code below.

That mode adds two buttons Cycle Dir Prev and Cycle Dir Next to the tool-bar.

Cycle Dir Prev finds the next older file in the default-directory of the current buffer and Cycle Dir Next finds the next newer buffer in the default-directory of the current buffer.

This has the side-effect that if you open a buffer in another directory you can change the directory to be cycled.

(require 'cl-macs)

(defconst cycle-dir-option-name "-oldest-in-dir"
  "Command line option for finding the oldest regular file
in the given directory.")

(defconst cycle-dir-predicate-option-name "-cycle-dir-predicate"
  "Command line option for setting the `cycle-dir-predicate'.
Expects a function with two file name arguments.
See `cycle-dir-predicate'.")

(defconst cycle-dir-filter-option-name "-cycle-dir-filter"
  "Command line option for setting the `cycle-dir-filter'.
The option value is a regular expression for filtering the file names.
See `cycle-dir-filter'.")

(defun cycle-dir-file-older-than-file-p (file1 file2)
  "Return t if file FILE1 is older than file FILE2.
If FILE2 does not exist, the answer is nil;
otherwise, if FILE1 does not exist, the answer is t."
  (file-newer-than-file-p file2 file1))

(defcustom cycle-dir-predicate #'cycle-dir-file-older-than-file-p
  "Function that takes two file name arguments.
It should return t if the first file should come before the second
when cycling."
  :type 'function
  :group 'cycle-dir)

(defcustom cycle-dir-filter nil
  "Regular expression for filtering the file names for cycling
or nil if all regular files should be cycled."
  :type 'regexp
  :group 'cycle-dir)

(setq inhibit-startup-screen t)

(defun cycle-dir-files (&optional dir)
  "Return list of files in directory DIR.
DIR defaults to `default-directory'.
The list is sorted by modification time from the newest to the oldest."
  (unless dir
    (setq dir default-directory))
  (when (and (stringp dir)
         (file-directory-p dir))
    (sort (cl-remove-if-not
       #'file-regular-p
       (directory-files dir t cycle-dir-filter t))
      cycle-dir-predicate)))

(defun cycle-dir-next-name (inc)
  "Get the next INC-th file name for `cycle-dir-next'.
See `cycle-dir-next' for the meaning of INC.
Return nil if there is no next file."
    (let* ((file-name (buffer-file-name))
       (files (cycle-dir-files))
       (n (cl-position file-name files :test #'string-equal))
       (n+inc (and n (+ n inc))))
      (and n (> n+inc 0) (nth n+inc files))))

(defun cycle-dir-next (&optional inc)
  "Replace the current buffer visiting a file with one visiting INC-th next file.
INC is an integer with default value 1.
For positive INC the next file is the INC-th next younger file.
For negative INC the next file is the INC-th next older file.
Kill current buffer if it is not modified."
  (interactive "p")
  (unless inc (setq inc 1))
  (let ((next (cycle-dir-next-name inc))
    (buf (current-buffer)))
    (when next
      (find-file next)
      (unless (buffer-modified-p buf)
    (kill-buffer buf)))))

(defun cycle-dir-prev ()
  "Call `cycle-dir-next' with inc value -1."
  (interactive)
  (cycle-dir-next -1))

(define-minor-mode cycle-dir-mode
  "Show buttons to get to the next newer or older file." nil nil nil
  (setq-local tool-bar-map tool-bar-map)
  (tool-bar-local-item "prev-node" 'cycle-dir-prev 'cycle-dir-prev tool-bar-map :enable '(cycle-dir-next-name -1) :visible 'cycle-dir-mode)
  (tool-bar-local-item "next-node" 'cycle-dir-next 'cycle-dir-next tool-bar-map :enable '(cycle-dir-next-name 1) :visible 'cycle-dir-mode))

(defun cycle-dir-mode-on ()
  "Turn `cycle-dir-mode' on for buffers associated with files."
  (when (buffer-file-name)
    (cycle-dir-mode)))

(define-global-minor-mode cycle-dir-global-mode cycle-dir-mode cycle-dir-mode-on)

(defvar cycle-dir-cmd-line-option-hook #'cycle-dir-global-mode)

(defun cycle-dir-cmd-line-option (switch)
  "Fetch arg of command line options -cycle-dir-predicate and -oldest-in-dir from `command-switch-alist'."
  (cond
   ((string-equal switch cycle-dir-predicate-option-name)
    (cl-assert command-line-args-left nil "Missing predicate for option -%s" cycle-dir-predicate-option-name)
    (setq cycle-dir-predicate (read (car command-line-args-left))
      command-line-args-left (cdr command-line-args-left))
    t)
   ((string-equal switch cycle-dir-filter-option-name)
    (cl-assert command-line-args-left nil "Missing regular expression for option -%s" cycle-dir-filter-option-name)
    (setq cycle-dir-filter (car command-line-args-left)
      command-line-args-left (cdr command-line-args-left))
    t)
   ((string-equal switch cycle-dir-option-name)
    (cl-assert command-line-args-left nil "Missing directory name for option -%s" cycle-dir-option-name)
    (let ((dir-name (car command-line-args-left)))
      (setq command-line-args-left (cdr command-line-args-left))
      (cl-assert (file-directory-p dir-name) nil "Value \"%s\" for option -%s is not a directory." dir-name cycle-dir-option-name)
      (let ((files (cycle-dir-files dir-name)))
    (cl-assert files nil "Directory \"%s\" for option -%s does not contain any regular files." dir-name cycle-dir-option-name)
    (find-file (car files))))
    (run-hooks 'cycle-dir-cmd-line-option-hook)
    t)))

(add-to-list 'command-switch-alist (cons cycle-dir-option-name #'cycle-dir-cmd-line-option))
(add-to-list 'command-switch-alist (cons cycle-dir-predicate-option-name #'cycle-dir-cmd-line-option))
(add-to-list 'command-switch-alist (cons cycle-dir-filter-option-name #'cycle-dir-cmd-line-option))
  • This doesn't seem to work for me. It doesn't actually open a file in the directory passed as a command line argument, it opens a file in the working directory. Yet it fails if the directory argument isn't there. Also, the file that it opens weirdly isn't the absolute oldest according to Nautilus's "Sort by date modified" feature. Once it opens, I see the buttons but they aren't activated. – firstname gklsodascb Jun 17 '18 at 13:41
  • When I do M-x and try "cycle-dir-next" I get "Symbol's function definition is void: when-let" – firstname gklsodascb Jun 17 '18 at 13:47
  • @firstnamegklsodascb The image in your question suggests that you are still using emacs24. Is that right? I've tested the stuff with emacs25.1.50.2. For emacs 25.1.50.2 when-let is contained in the package subr-x. github.com/jkitchin/scimax/issues/55 suggests that emacs24 does not have when-let. Let's see whether we can do without it. – Tobias Jun 17 '18 at 13:55
  • Yes, I'm still on emacs24. – firstname gklsodascb Jun 17 '18 at 14:22
  • @firstnamegklsodascb I've removed when-let and fixed some bugs. Could you try again? – Tobias Jun 17 '18 at 15:16
1

You can use Dired+ to do it.

  1. C-x d to visit your directory in Dired mode.

  2. Use s to sort the listing chronologically, most recent first. (You can also get this sort order at the outset, by default, if you want.)

  3. Use C-down and C-up to cycle among files chronologically. They move to the next or previous file or directory listed, keeping the focus in the Dired buffer, but they also visit the file or directory itself in a separate window.

You can also use e to show or hide the file or directory named on the current line.

Each time you show a file or directory, the buffer for the one previously shown this way is killed.

This gives you a quick preview, but also more than that. You can use C-x o to switch to the displayed file or directory and edit it.

You can customize user options diredp-visit-ignore-extensions and diredp-visit-ignore-regexps, to specify names of files and directories to be ignored, so they are not visited. For example, you might want to skip over compiled files or backup files or autosave files.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.