I have written an Emacs extension that runs arbitrary functions when a buffer is saved. Configuration is stored per Git repo, in a .graffitist file with the following format:
(setq graffitist-rules
'((".*" . (lambda (file-name project-dir-name) ... ))))
That is, if the saved buffer filename matches the regex ".*", the provided function is executed. The Emacs Lisp code responsible for this is as follows:
(defun graffitist--run-actions-for-file ()
"Runs the action specified in the project .graffitist file for the filename of the current buffer, if any."
(let* ((filename (buffer-file-name (current-buffer)))
(project-directory (graffitist--find-project-dir filename))
(config-filename (graffitist--config-filename project-directory))
(action (graffitist--find-action config-filename filename)))
(if action
(funcall action filename project-directory))))
(defun graffitist--find-project-dir (filename)
"Finds the project directory for the specified filename. Returns nil if there is no project directory.
The project directory is defined as the first directory upwards in the hierarchy containing .git."
(let ((directory-name (locate-dominating-file filename ".git")))
(if directory-name
(file-name-as-directory directory-name)
nil)))
(defun graffitist--config-filename (project-directory)
"Returns the filename to the .graffitist configuration file for the specified project directory."
(if project-directory
(concat project-directory ".graffitist")
nil))
(defun graffitist--find-action (config-filename filename)
"Finds the first action associated with a regex that matches filename."
(if (and config-filename (file-exists-p config-filename))
(progn
(load config-filename)
(assoc-default filename graffitist-rules #'string-match))))
(add-hook 'after-save-hook #'graffitist--run-actions-for-file)
This works, but seems a bit odd. It's loading the .graffitist file every time a buffer is saved, which is expensive. Also, there's just the one graffitist-rules global that's updated each time a buffer is saved.
Is there a more idiomatic way of doing this in Emacs Lisp? That is, loading per-buffer configuration and keeping it current should the configuration file change?
Perhaps once you load a given config file, you could cache it and then make use of notifications on file changes to watch for changes on that file. If a watched file changes, clear it from the cache so it gets reloaded next time it's needed.
Related
I keep all tasks in a todo.org file. After a task is done, I'd like to select and archive it to a separate archive.org file. I want to set this up in my config file, of course, not as a per-file header.
I tried this:
(setq org-archive-location (concat org-directory "~/Dropbox/logs/archive.org::"))
Which seems to work; I see a message that the task has been archived to my chosen path. But--when I open archive.org, nothing is there. Ever. Then, whenever I close and reopen Emacs, I see the error message Symbol's value as variable is void: org-directory.
So: How do I properly configure Emacs to archive tasks I choose to the correct place? Still an Org mode neophyte, so have mercy.
EDIT: Using Emacs 25.3.2, and Org 9.1.7.
Inasmuch as the O.P. has also expressed (in a comment underneath the original question) a desire to automatically save the target archive buffer, this answer addresses that as well:
(require 'org)
(setq org-archive-location "~/Dropbox/logs/archive.org::")
(defun org-archive-save-buffer ()
(let ((afile (org-extract-archive-file (org-get-local-archive-location))))
(if (file-exists-p afile)
(let ((buffer (find-file-noselect afile)))
(if (y-or-n-p (format "Save (%s)" buffer))
(with-current-buffer buffer
(save-buffer))
(message "You expressly chose _not_ to save (%s)" buffer)))
(message "Ooops ... (%s) does not exist." afile))))
(add-hook 'org-archive-hook 'org-archive-save-buffer)
In emacs dired I want to do something I do quite often in Microsoft PowerShell.
In PowerShell, I have a set of folders that I always use, and I assign their full path to global variables in my profile script (similar to init.el in the emacs world) e.g:
$standardTemp = "C:\Long\Path\To\Folder"
If I am in another folder and I want to copy something to the above folder, I do:
copy myFile $standardTemp
Even more useful as a feature, is if I put a backslash after $standardTemp, it will expand it out, so I can go into subfolders if I need to. This is a very awesome feature and saves me a lot of time.
With the dired copy command can I do something similar, if I define variables with e.g. setq in my init.el file?
How about something like this?
;; Use ido
(require 'ido)
(ido-mode t)
;; Make a hash table to hold the paths
(setq my-target-dirs (make-hash-table :test 'equal))
;; Put some paths in the hash (sorry for Unix pathnames)
(puthash "home" "/home/jhrr/" my-target-dirs)
(puthash "target" "/home/jhrr/target/" my-target-dirs)
;; A function to return all the keys from a hash.
(defun get-keys-from-hash (hash)
(let ((keys ()))
(maphash (lambda (k v) (push k keys)) hash)
keys))
;; And the function to prompt for a directory by keyword that is looked
;; up in the hash-table and used to build the target path from the
;; value of the lookup.
(defun my-dired-expand-copy ()
(interactive)
(let* ((my-hash my-target-dirs)
(files (dired-get-marked-files))
(keys (get-keys-from-hash my-hash)))
(mapc (lambda (file)
(copy-file file
(concat
(gethash
(ido-completing-read
(concat "copy " file " to: ") keys) my-hash)
(file-name-nondirectory file))))
files)))
It's not exhaustively tested as I just whipped it up in 10 minutes, but it does the job and it can handle multiple files.
You will need to open the dired buffer in the directory the files are in and mark each file you want to copy with 'm', then invoke my-dired-expand-copy and it will prompt you for a target destination (in the form of a keyword from the hash-table we set-up) for the file before, finally, copying the file over to the directory that maps to the target keyword.
It doesn't quite cover the sub-directories use-case you mention, but it shouldn't be too hard to get there given a bit more hacking.
UPDATE:
This should now prompt you to be able to descend into subdirectories from an original target; maybe not the most mind-shatteringly wonderful UX on the whole, but, it works:
(defun my-dired-expand-copy-2 ()
(interactive)
(let* ((my-hash my-target-dirs)
(files (dired-get-marked-files))
(keys (get-keys-from-hash my-hash)))
(mapc (lambda (file)
(let ((target (gethash
(ido-completing-read
(concat "copy " file " to: ") keys) my-hash)))
(if (y-or-n-p "Descend?")
;; Descend into subdirectories relative to target dir
(let ((new-target (ido-read-directory-name "new dir: " target)))
(copy-file file (concat new-target
(file-name-nondirectory file)))
(message (concat "File: " file " was copied to " new-target)))
;; Else copy to root of originally selected directory
(copy-file file (concat target (file-name-nondirectory file)))
(message (concat "File: " file " was copied to " target)))))
files)))
When I need to use dired to get to frequently-used directories, I use the standard emacs bookmarking capabilities.
I manually navigate to the directory, and press
C-x r m
to execute the command
bookmark-set
You'll be prompted for a name for the bookmark. Enter a shortcut that you can remember.
At this point, anytime you want to open that directory within dired, simply execute the command
bookmark-jump
with the keys
C-x r b
Enter your shortcut to the directory, and dired will open to that location.
To copy from one directory to another, ensure you have the following set in your init file
(setq dired-dwim-target t)
Then you can open a dired window for the source directory, and another window for the target directory within in the same frame, and dired will automatically assign the source and target location to the appropriate directories.
Note this is just a subset of what emacs bookmarks can do for you!
Chris
In addition to using bookmarks, consider using directory-name aliases (e.g. symlinks) or directory-abbrev-alist. See the Emacs manual, node File Aliases.
If you want to insert the value of an environment variable into the minibuffer, you can do it this way:
C-u M-: (getenv "THE-VARIABLE")
where THE-VARIABLE is the variable name. Using C-u inserts the value of evaluating the sexp into the current buffer (in this case the minibuffer).
So you would, say, use C to copy the marked files in Dired, and then use C-u with a getenv sexp for the existing variable you have, to insert its value into the minibuffer when prompted for the directory to copy to.
(Depending on your Emacs setup, you might need to set enable-recursive-minibuffers to non-nil, to be able to use M-: from the minibuffer.)
I'm using Emacs.
Is there any way to add hook on a function?
Assume that there is a markdown-export function.
It is designed to export HTML file into current directory where current working 'markdown file' exsits.
But, I want to export HTML file into another directory. How can I do that without modification on Emacs markdown plugin (markdown-mode.el)?
This is markdown-mode.el's export function:
(defun markdown-export (&optional output-file)
"Run Markdown on the current buffer, save to file, and return the filename.
If OUTPUT-FILE is given, use that as the filename. Otherwise, use the filename
generated by `markdown-export-file-name', which will be constructed using the
current filename, but with the extension removed and replaced with .html."
(interactive)
(unless output-file
(setq output-file (markdown-export-file-name ".html")))
(when output-file
(let* ((init-buf (current-buffer))
(init-point (point))
(init-buf-string (buffer-string))
(output-buffer (find-file-noselect output-file))
(output-buffer-name (buffer-name output-buffer)))
(run-hooks 'markdown-before-export-hook)
(markdown-standalone output-buffer-name)
(with-current-buffer output-buffer
(run-hooks 'markdown-after-export-hook)
(save-buffer))
;; if modified, restore initial buffer
(when (buffer-modified-p init-buf)
(erase-buffer)
(insert init-buf-string)
(save-buffer)
(goto-char init-point))
output-file)))
=====================================================================
I have made an advice to save exported HTML at temp directory
Here is the code.
(defadvice markdown-export (around set-temp-path-for-exported-file activate)
(ad-set-arg 0 (format "%s/%s" "~/.emacs.d/temp-dir" (file-name-nondirectory buffer-file-name)))
ad-do-it)
Thanks!!!!!!!!!!!!!!
In this case you do not need to hook on this function since it already accepts the filename as an argument, unfortunately it does not accept the filename when called interactively. As a workaround you can define a simple wrapper around the function like follows
(defun my-markdown-export (&optional file)
(interactive (list (ido-read-file-name "Export as: ")))
(markdown-export file))
The advice mechanism is a bit like having hooks for any arbitrary function, but here you have actual hooks you can use, as well as a function argument which addresses your requirement directly.
So you can:
(a) Pass the function any arbitrary output filename.
(b) Use the provided markdown-before-export-hook to setq whichever variables you need to (which at a glance looks like output-file, output-buffer, and output-buffer-name).
I am looking to create a custom config for emacs to use for Erlang work and I want to refer to my custom EDTS repo as being under the directory from which init.el was loaded. Right now I have this:
(add-to-list 'load-path "~/.emacs-edts/edts/")
But I would rather not hardcode it and refer to it by variable.
Suggestions?
Strictly speaking the answer is (file-name-directory user-init-file), but instead see C-hv user-emacs-directory
I have the following snippet in my init.el:
(setq my-init-dir
(file-name-directory
(or load-file-name (buffer-file-name))))
This has the advantage of working whether init.el is in your emacs.d directory or not.
I have the following in my init file:
(defun my-file-name-basename (s)
"The directory name, without the final part.
For example:
(my-file-name-basename \"alpha/beta/gamma\") => \"alpha/beta\""
(substring (file-name-directory s) 0 -1))
;; Note: Normally, it's not possible to find out the file a specific
;; function is defined in. However, it's possible to save the file
;; name at the time this file was loaded.
(defvar my-load-file-name load-file-name
"The file name of this file.")
(defun my-start-directory (&optional path)
"The root directory that contains this module.
When PATH is specified, return the start directory concatenated with PATH.
Otherwise return the directory with a trailing slash."
;; Note: Try to figure out where we are, so that we can add the
;; subdirectories. `load-file-name' only works when the file is
;; loaded. Picking up the file from the symbol works when this is
;; evaluated later.
(let ((file-name (or my-load-file-name
(symbol-file 'my-start-directory)
;; Default value. (This is used, for example,
;; when using `eval-buffer' or `eval-region'.)
"~/emacs")))
(let ((start (concat (my-file-name-basename
(my-file-name-basename file-name))
"/")))
(if path
(concat start path)
start))))
In addition to finding out where the file containing the above above code is located (which does not have to be the init file), it provides a convenient way to create paths based on it. For example:
(setq custom-file (my-start-directory "init/custom.el"))
I would like emacs to mark files that are generated as read-only when they're opened. The part of the puzzle that I'm missing is how to check if a file "exists". I currently have the following:
;;
;; get file extension
;;
(defun get-ext (file-name)
(car (cdr (split-string file-name "\\."))))
;;
;; get the base name of the file
;;
(defun base-name (file-name)
(car (split-string file-name "\\.")))
;;
;; if an 'lzz' file exists for this header, mark it as read only
;;
(defun mark-read-only ()
(if (string= (get-ext (cur-file)) "h")
(if ( ??file-exists??? (concat (base-name (cur-file)) ".lzz") )
(toggle-read-only))))
What can I use for "???file-exists???"?
Once I find this, I'll add "mark-read-only" to the appropriate hook (which I think is the find-file-hook).
BACKGROUND
We use lzz as a code generator to simplify our C/C++ development process. Briefly, lzz takes a single input file (which looks very like C/C++) and generates header and source files as appropriate.
By default, lzz includes #line directives so that the debugger points to the original source and not the generated source, however, to reduce compilation dependencies we normally disable these directives in header files. The result is that when debugging templates or inline functions, the debugger normally points to the generated header file and not the original source file.
This is not a big deal, however, recently I've found that when debugging I'll make a quick modification to the displayed file and then I'll rebuild. Of course this normally means that the change I made disappears because the file I edited is generated and so the changes are "blown away" during the library rebuild.
SOLUTION
Thanks to everyone for their help and comments. A special thanks to cobbal for pointing out the correct function to use.
Here's the resulting code (with updates based on the other comments here too):
(defun cur-file ()
"Return the filename (without directory) of the current buffer"
(file-name-nondirectory (buffer-file-name (current-buffer)))
)
(defun mark-generated-as-read-only ()
"Mark generated source files as read only.
Mark generated files (lzz or gz) read only to avoid accidental updates."
(if
(or (string= (file-name-extension (cur-file)) "h")
(string= (file-name-extension (cur-file)) "cpp"))
(cond
(
(file-exists-p (concat (file-name-sans-extension (cur-file)) ".lzz"))
(toggle-read-only))
(
(file-exists-p (concat (file-name-sans-extension (cur-file)) ".gz") )
(toggle-read-only))
)
)
)
try file-exists-p
"Return t if file filename exists (whether or not you can read it.)".
Note that it's not spesific to files and works for directories too.
Depending on what you need, you might want file-readable-p instead of file-exists-p.
Apropos will only get you so far. Icicles provides apropos completion and progressive completion which let you find help easily for command, function, variable, etc. names that match subparts in an arbitrary order (is it file-exists-p or exists-file-p?).
Use f.el, modern library for file and directory manipulation. You can use f-exists?, f-file?, f-directory? and many other predicates. The library is better than standard functions, because it's every file related function you'll ever need under one namespace.