Functional Fixes for Emacs
Yeah, yeah, yeah…I love Emacs, but some functions could make better assumptions to what you want, and of course, we can waste any good bindings. For instance, a function may require a region to be selected before working, but a keybinding shouldn’t. It should make good assumptions, where the ‘region’ would simply override those assumptions.
Case in point, is the ‘line’ work bound to
Ctrl-k … cut to the end
of the line, fine, but giving it a prefixed number will cut multiple
lines, but not the full text of the current line…like why would you
ever do that?
Note: I want to look at the old Tiny Tools project and steal their deletion code.
I like the
align- collection of functions, however, setting the
regular expression for
align-regexp can be daunting to remember
for some tasks.
For instance, I like to have my maps and hashtables and other
variable assignments aligned, but calling
interactively leaves a bit to be desired.
This amounts to two primary steps:
- Move each line in the region to the column specified by the first line
- Increase the spaces (after the initial indention) so that this
“second column” lines up (done with
(defun align-variables (start end) "Attempts to align all variables in an assignment list or keys in a hash table. For instance: (\"org-mode\" :base-extension \"org\" :recursive t :headline-levels 4 ; Just the default for this project. :auto-sitemap t ; Generate sitemap.org automagically ) Turns into the following if the region begins on the first line with the colon: (\"org-mode\" :base-extension \"org\" :recursive t :headline-levels 4 ; Just the default for this project. :auto-sitemap t ; Generate sitemap.org automagically ) Note: This currently does not align the comments. All lines in region will be indented to the position of the first line. For most languages/modes, this should be sufficient, but if it doesn't work, start the region as the column they should all be indented. For instance: var x = 10, start = beginningOfFile, end = File.end(); Start the region at the x, to achieve: var x = 10, start = beginningOfFile, end = File.end();" (interactive "r") (save-excursion (goto-char start) (let* ((times (count-lines start end)) (real-start (if (looking-at-p "[ \\t(]") (1- (search-forward-regexp "[^ \\t(]" end t)) start)) (real-end nil) ;; Will be set later (dest-column (progn (goto-char real-start) (current-column)))) ;; Step 1. Align all lines to the column of the text in the first line (dotimes (line times) (forward-line) (indent-line-to dest-column)) (setq real-end (point)) ;; Step 2. Align all the values in a second column (align-regexp real-start real-end "\\(\\s-*\\)\\(\\S-*\\)\\(\\s-*\\)" 3 1 nil))))
Kill Entire Lines
According to this article, killing the rest of the line is fine,
C-3 C-k kills only 2½ lines. Not so useful.
This creates a macro that moves to the beginning of the line and then calls a function given to it. Quite an interesting approach:
(defmacro bol-with-prefix (function) "Define a new function which calls FUNCTION. Except it moves to beginning of line before calling FUNCTION when called with a prefix argument. The FUNCTION still receives the prefix argument." (let ((name (intern (format "ha/%s-BOL" function)))) `(progn (defun ,name (p) ,(format "Call `%s', but move to the beginning of the line when called with a prefix argument." function) (interactive "P") (when p (forward-line 0)) (call-interactively ',function)) ',name)))
And we re-bind them to functions that use them.
(global-set-key [remap paredit-kill] (bol-with-prefix paredit-kill)) (global-set-key [remap sp-kill-hybrid-sexp] (bol-with-prefix sp-kill-hybrid-sexp)) (global-set-key [remap org-kill-line] (bol-with-prefix org-kill-line)) (global-set-key [remap kill-line] (bol-with-prefix kill-line)) (global-set-key (kbd "C-k") (bol-with-prefix kill-line))
While you can type:
M-4 M-@ to select the next four words, that
is too much finger stretching for something I want to use often.
M-@ four times is actually easier, but…)
M-w does nothing different with a prefix, so this seems quite
reasonable to re-purpose such a direct command:
(defun ha/select-words-or-copy (num) "If region is active, copy to kill ring as normal, but given a prefix, selects that number of words." (interactive "p") (cond ((use-region-p) (kill-ring-save (region-beginning) (region-end))) ((> num 0) (progn (beginning-of-thing 'word) (push-mark (point) nil t) (forward-word num))) ((< num 0) (progn (end-of-thing 'word) (push-mark (point) nil t) (forward-word num)))))
Now bind them to the standard keys:
(global-set-key (kbd "M-w") 'ha/select-words-or-copy)
paredit and other modes automatically insert final
characters like semi-colons and parenthesis, what I really want is
to hit return from the end of the line. Pretty simple function.
(defun newline-for-code () "Inserts a newline character, but from the end of the current line." (interactive) (move-end-of-line 1) (newline-and-indent))
And we can bind that to the free, Meta-Return:
(global-set-key (kbd "M-RET") 'newline-for-code)
Remember, this works everywhere except for org-mode.
I like how
M-SPC removes all but one space, and
M-\ removes all
spaces. Would be nice to remove all newlines in the same way.
C-x C-o removes all following newlines, so if at the end of
the first line that should be joined, then this acts somewhat
(defun join-lines () "If at the end of the line, will join the following line to the end of this one...unless it is blank, in which case, it will keep joining lines until the next line with text is connected." (interactive) ;; Move to the the beginning of the white space before attempting ;; this process. This allows us to join lines even if we are in the ;; middle of some empty lines. (re-search-backward "[^[:space:]\\r\\n]") (forward-char) ;; Just in case we have some trailing whitespace we can't see, let's ;; just get rid of it. Won't do anything if in the middle of a line, ;; or if there is not trailing whitespace. (delete-trailing-whitespace (point) (point-at-eol)) ;; While we are at the end of the line, join a line, remove the ;; whitespace, and keep on going until we're through... (while (eq (point-at-eol) (point)) (delete-char 1) (delete-trailing-whitespace (point) (point-at-eol)))) (global-set-key (kbd "C-RET") 'join-lines)
I would like to have
M-RET remove the lines similar to the way
M-SPC works, but that is already bound in
org-mode to making a
special header, so I’ll just bind it to Control.
The iy-go-to-char project allows a quick search for a particular
character. In Episode 6 of EmacsRocks, Magnar Sveen pulls it all
together and makes a compelling case for micro-optimizations.
I find it better than
avy when in a macro.t
(use-package iy-go-to-char :ensure t :bind ("C-`" . iy-go-to-char) ("<f13>" . iy-go-to-char) ("C-~" . iy-go-to-char-backward))
To use, type
C-` and then a character, number or other symbol to
jump to. Typing most things will bugger out of its “state” and
start editing, however, typing:
;will jump to the next occurrence of that letter
- =,= jumps backwards
C-wcuts from where the cursor started and where it ended.
M-wcopies that region
Better Beginning of Line
This Emacs Redux article has a great suggestion for having
to the beginning of the line’s content instead of the actual
beginning of the line. Hit
C-a a second to get to the actual
(defun smarter-move-beginning-of-line (arg) "Move point back to indentation of beginning of line. Move point to the first non-whitespace character on this line. If point is already there, move to the beginning of the line. Effectively toggle between the first non-whitespace character and the beginning of the line. If ARG is not nil or 1, move forward ARG - 1 lines first. If point reaches the beginning or end of the buffer, stop there." (interactive "^p") (setq arg (or arg 1)) ;; Move lines first (when (/= arg 1) (let ((line-move-visual nil)) (forward-line (1- arg)))) (let ((orig-point (point))) (back-to-indentation) (when (= orig-point (point)) (move-beginning-of-line 1)))) ;; remap C-a to `smarter-move-beginning-of-line' (global-set-key [remap move-beginning-of-line] 'smarter-move-beginning-of-line) (global-set-key [remap org-beginning-of-line] 'smarter-move-beginning-of-line)
Next and Previous File
Sometimes it is obvious what is the next file based on the one I’m currently reading. For instance, in my journal entries, the filename is a number that can be incremented. Same with presentation files…
(defun split-string-with-number (string) "Returns a list of three components of the string, the first is the text prior to any numbers, the second is the embedded number, and the third is the rest of the text in the string." (let* ((start (string-match "[0-9]+" string)) (end (string-match "[^0-9]+" string start))) (if start (list (substring string 0 start) (substring string start end) (if end (substring string end) "")))))
Which means that the following defines this function:
(split-string-with-number "abc42xyz") ;; ("abc" "42" "xyz") (split-string-with-number "42xyz") ;; ("" "42" "xyz") (split-string-with-number "abc42") ;; ("abc" "42" "") (split-string-with-number "20140424") ;; ("" "20140424" "") (split-string-with-number "abcxyz") ;; nil
Given this splitter function, we create a function that takes some sort of operator and return a new filename based on the conversion that happens:
(defun find-file-number-change (f) (let* ((filename (buffer-file-name)) (parts (split-string-with-number (file-name-base filename))) (new-name (number-to-string (funcall f (string-to-number (nth 1 parts)))))) (concat (file-name-directory filename) (nth 0 parts) new-name (nth 2 parts))))
And this allows us to create two simple functions that can load the “next” and “previous” files:
(defun find-file-increment () "Takes the current buffer, and loads the file that is 'one more' than the file contained in the current buffer. This requires that the current file contain a number that can be incremented." (interactive) (find-file (find-file-number-change '1+))) (defun find-file-decrement () "Takes the current buffer, and loads the file that is 'one less' than the file contained in the current buffer. This requires that the current file contain a number that can be decremented." (interactive) (find-file (find-file-number-change '1-))) (global-set-key (kbd "C-c f +") 'find-file-increment) (global-set-key (kbd "C-c f n") 'find-file-increment) (global-set-key (kbd "C-c f -") 'find-file-decrement) (global-set-key (kbd "C-c f p") 'find-file-decrement)
Kill This Buffer
I rarely want to kill any buffer but the one I’m looking at.
(global-set-key (kbd "C-x k") 'kill-this-buffer) (global-set-key (kbd "C-x K") 'kill-buffer)
Better Transpose Word
Seems that the prefix feature of the
traspose-words function is
backwards… at least, for case my use whatever
(defun ha/transpose-words (pre) "Wrapper around `transpose-words' and `org-transpose-words' that works more intuitively when you are at the end of the line." (interactive "p") (let ((wrap-this (if (equal major-mode 'org-mode) 'org-transpose-words 'transpose-words))) ;; At the end of the line with a positive prefix? (when (looking-at "\s*$") (cond ;; No prefix? Send last word back before the previous... ((null pre) (setq pre -1)) ;; Call it with a negative version of the prefix... ((> pre 0) (setq pre (- 0 pre))))) (funcall wrap-this pre))) (add-hook 'org-mode-hook (lambda() (define-key org-mode-map [remap org-transpose-words] #'ha/transpose-words))) (global-set-key [remap transpose-words] #'ha/transpose-words)
Make sure that we can simply
require this library.
Before you can build this on a new system, make sure that you put
the cursor over any of these properties, and hit: