Skip to content

Latest commit

 

History

History
383 lines (309 loc) · 14.2 KB

emacs-fixes.org

File metadata and controls

383 lines (309 loc) · 14.2 KB

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.

Aligning Variables

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 align-regexp interactively leaves a bit to be desired.

This amounts to two primary steps:

  1. Move each line in the region to the column specified by the first line
  2. Increase the spaces (after the initial indention) so that this “second column” lines up (done with align-regexp).
(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))))

Cut Line or Region

Hitting Ctrl-w cuts the text from the point to the mark…but do you remember where that fucker is? Probably not, and may not be what you want, so unless the region is currently active and highlighted, let’s cut the current line … all of it … no matter where the point is. This idea, came from ErgoEmacs, but with a little modification.

First, let’s define a function that returns the first and last text positions based on if the region is “on” and what the mode is. That is, if I’m in org-mode, I probably don’t want to cut/copy the line, but the paragraph or other structural element.

(defun ha/get-line-or-region (&optional num)
  "Return a list containing the start and ending text positions
of the active region.  If the region isn't active, make a good
guess about the structure where the start and end of the current
line is the default. If a prefix number is given, it returns the
position that includes that many lines or other structural
elements."
  (cond
   ((use-region-p)  (list (region-beginning)        (region-end)))
   ((string-equal major-mode "org-mode")
    (list (ha/get-org-element 'org-backward-paragraph '< num)
          (ha/get-org-element 'org-forward-paragraph '> num)))
   (num             (list (line-beginning-position) (line-end-position (1+ num))))
   (t               (list (line-beginning-position) (line-end-position)))))

(defun ha/get-org-element (f p &optional num)
  "Strange little function.  F is a function to call in an saved
excursion to move the cursor around and get its state. P is a
predicate on whether NUM should be given to F, since number could
be nil.  Written to make 'ha/get-line-or-region' easier to read."
  (save-excursion
    (if (and num (funcall p num 0))
        (dotimes (i (abs num))
                 (funcall f))
      (funcall f))
    (point)))

Now, we can use this function to copy the text and bind it to our normal key chord:

(defun ha/copy-line-or-region ()
  "Copy current line or text selection.  If prefix argument is given, copies that many lines."
  (interactive)
  (apply 'kill-ring-save (ha/get-line-or-region current-prefix-arg)))

(global-set-key (kbd "M-w") 'ha/copy-line-or-region)
(global-set-key (kbd "A-c") 'ha/copy-line-or-region)
(global-set-key (kbd "s-c") 'ha/copy-line-or-region)

And put on the cut ring:

(defun ha/cut-line-or-region ()
  "Cut current line or text selection.  If prefix argument is given, copies that many lines."
  (interactive)
  (apply 'kill-region (ha/get-line-or-region current-prefix-arg)))

(global-set-key (kbd "C-w") 'ha/cut-line-or-region)
(global-set-key (kbd "A-x") 'ha/cut-line-or-region)
(global-set-key (kbd "s-x") 'ha/cut-line-or-region)

Better Newline

Since 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.

Join Lines

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.

Sure, 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 like M-SPC.

(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.

Better Movement

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. We’ll see if I can remember to use the feature.

(when (require 'iy-go-to-char nil t)
  (global-set-key (kbd "C-`") 'iy-go-to-char)
  (global-set-key (kbd "<f13>") 'iy-go-to-char)
  (global-set-key (kbd "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-w cuts from where the cursor started and where it ended.
  • M-w copies that region

Better Beginning of Line

This Emacs Redux article has a great suggestion for having C-a go 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 beginning.

(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'
;; however, this doesn't work in org-mode's `org-beginning-of-line'
(global-set-key [remap move-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)

Technical Artifacts

Make sure that we can simply require this library.

(provide 'init-fixes)

Before you can build this on a new system, make sure that you put the cursor over any of these properties, and hit: C-c C-c