├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE.md
└── PULL_REQUEST_TEMPLATE.md
├── CHANGELOG.md
├── CONTRIBUTING.md
├── README.md
└── crux.el
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: bbatsov
4 | patreon: bbatsov
5 | ko_fi: bbatsov
6 | liberapay: bbatsov
7 | custom: https://www.paypal.me/bbatsov
8 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | *Use the template below when reporting bugs. Please, make sure that
2 | you're running the latest stable crux and that the problem you're reporting
3 | hasn't been reported (and potentially fixed) already.*
4 |
5 | **Remove all of the placeholder text in your final report!**
6 |
7 | ## Expected behavior
8 |
9 | ## Actual behavior
10 |
11 | ## Steps to reproduce the problem
12 |
13 | *This is extremely important! Providing us with a reliable way to reproduce
14 | a problem will expedite its solution.*
15 |
16 | ## Environment & Version information
17 |
18 | ### crux version information
19 |
20 | *Include here the version string displayed by `M-x
21 | crux-version`. Here's an example:*
22 |
23 | ```
24 | crux version: 0.1
25 | ```
26 |
27 | ### Emacs version
28 |
29 | *E.g. 24.5* (use C-h C-a to see it)
30 |
31 | ### Operating system
32 |
33 | *E.g. Windows 10*
34 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | **Replace this placeholder text with a summary of the changes in your PR.
2 | The more detailed you are, the better.**
3 |
4 | -----------------
5 |
6 | Before submitting a PR make sure the following things have been done (and denote this
7 | by checking the relevant checkboxes):
8 |
9 | - [ ] The commits are consistent with our [contribution guidelines](../blob/master/CONTRIBUTING.md)
10 | - [ ] The new code is not generating bytecode or `M-x checkdoc` warnings
11 | - [ ] You've updated the [changelog](../blob/master/CHANGELOG.md) (if adding/changing user-visible functionality)
12 | - [ ] You've updated the readme (if adding/changing user-visible functionality)
13 |
14 | Thanks!
15 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## master (unreleased)
4 |
5 | ### New features
6 |
7 | * [#101](https://github.com/bbatsov/crux/pull/101): Add `crux-find-current-directory-dir-locals-file`.
8 | * Add `crux-keyboard-quit-dwim`.
9 |
10 | ### Bugs fixed
11 |
12 | * Create nonexistent parent directories in `crux-copy-file-preserve-attributes`.
13 |
14 | ## 0.5.0 (2024-02-29)
15 |
16 | ### New features
17 |
18 | * [#94](https://github.com/bbatsov/crux/pull/94): Add `crux-with-region-or-sexp-or-line`.
19 | * [#92](https://github.com/bbatsov/crux/pull/92): Consider derived modes when checking for major mode (`dired`, `org-mode`, `eshell`).
20 |
21 | ### Bugs fixed
22 |
23 | * More robust `crux-rename-file-and-buffer`.
24 | * Fix `sudo` not found error in OpenBSD and Alpine Linux (they use `doas`).
25 | * [#100](https://github.com/bbatsov/crux/pull/100): More robust `crux-copy-file-preserve-attributes`.
26 |
27 | ## 0.4.0 (2021-08-10)
28 |
29 | ### New features
30 |
31 | * [#65](https://github.com/bbatsov/crux/pull/65): Add a configuration option to move using visual lines in `crux-move-to-mode-line-start`.
32 | * [#72](https://github.com/bbatsov/crux/pull/72): Add `crux-kill-buffer-truename`. Kills path of file visited by current buffer.
33 | * [#78](https://github.com/bbatsov/crux/pull/78): Add `crux-recentf-find-directory`. Open recently visited directory.
34 | * Add `crux-copy-file-preserve-attribute`.
35 | * Add `crux-find-user-custom-file`.
36 | * Add `crux-kill-and-join-forward`.
37 | * Add `crux-other-window-or-switch-buffer`.
38 | * Add support for org-mode links in `crux-view-url`.
39 | * Add support for creating shell and terminal buffers.
40 | * Add remote files support to `crux-sudo-edit`.
41 | * Add `crux-smart-kill-line`.
42 |
43 | ### Changes
44 |
45 | * Remove unused prefix argument from `crux-smart-kill-line`.
46 | * Mark `crux-recentf-ido-find-file` as obsolete.
47 |
48 | ### Bugs fixed
49 |
50 | * Fixed extra line issue when duplicating region.
51 | * Various small fixes that we were too lazy to document properly.
52 | * Fixed `sudo` not found in OpenBSD and Alpine Linux.
53 |
54 | ## 0.3.0 (2016-05-31)
55 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | If you discover issues, have ideas for improvements or new features, or
4 | want to contribute a new module, please report them to the
5 | [issue tracker][1] of the repository or submit a pull request. Please,
6 | try to follow these guidelines when you do so.
7 |
8 | ## Issue reporting
9 |
10 | * Check that the issue has not already been reported.
11 | * Check that the issue has not already been fixed in the latest code
12 | (a.k.a. `master`).
13 | * Be clear, concise and precise in your description of the problem.
14 | * Open an issue with a descriptive title and a summary in grammatically correct,
15 | complete sentences.
16 | * Include any relevant code to the issue summary.
17 |
18 | ## Pull requests
19 |
20 | * Read [how to properly contribute to open source projects on Github][2].
21 | * Use a topic branch to easily amend a pull request later, if necessary.
22 | * Write [good commit messages][3].
23 | * Mention related tickets in the commit messages (e.g. `[Fix #N] Add missing autoload cookies`)
24 | * Update the [changelog][5].
25 | * Use the same coding conventions as the rest of the project.
26 | * Verify your Emacs Lisp code with `checkdoc` (C-c ? d).
27 | * Open a [pull request][4] that relates to *only* one subject with a clear title
28 | and description in grammatically correct, complete sentences.
29 |
30 | [1]: https://github.com/bbatsov/crux/issues
31 | [2]: http://gun.io/blog/how-to-github-fork-branch-and-pull-request
32 | [3]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
33 | [4]: https://help.github.com/articles/using-pull-requests
34 | [5]: https://github.com/bbatsov/crux/blob/master/CHANGELOG.md
35 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [![License GPL 3][badge-license]][copying]
2 | [![MELPA][melpa-badge]][melpa-package]
3 | [![MELPA Stable][melpa-stable-badge]][melpa-stable-package]
4 |
5 | # crux
6 |
7 | A **C**ollection of **R**idiculously **U**seful e**X**tensions for Emacs.
8 | crux bundles many useful interactive commands to enhance your
9 | overall Emacs experience.
10 |
11 | Most of the crux commands are related to the editing experience, but
12 | there are also a bunch of utility commands that are just very useful
13 | to have (e.g. `crux-open-with` and `crux-reopen-as-root`).
14 |
15 | ## Origins of crux
16 |
17 | Many of the functions in crux started life as blog posts on
18 | [Emacs Redux](https://emacsredux.com), then were included in
19 | [Emacs Prelude](https://www.github.com/bbatsov/prelude), before finally
20 | being [extracted](https://emacsredux.com/blog/2016/01/30/crux/)
21 | to crux. You can see a full list of blog posts on functions
22 | in crux on the [tags page](https://emacsredux.com/tags/#crux).
23 |
24 | ## Installation
25 |
26 | Available on all major `package.el` community maintained repos -
27 | [MELPA Stable][] and [MELPA][] repos.
28 |
29 | MELPA Stable is recommended as it has the latest stable version.
30 | MELPA has a development snapshot for users who don't mind breakage but
31 | don't want to run from a git checkout.
32 |
33 | You can install `crux` using the following command:
34 |
35 | M-x package-install [RET] crux [RET]
36 |
37 | If the installation doesn't work try refreshing the package list:
38 |
39 | M-x package-refresh-contents
40 |
41 | Alternatively, you can add the following code to your Emacs config:
42 |
43 | ```el
44 | (unless (package-installed-p 'crux)
45 | (package-refresh-contents)
46 | (package-install 'crux))
47 | ```
48 |
49 | ## Keybindings
50 |
51 | crux doesn't setup any keybindings for its commands out-of-the-box.
52 | There are several reasons for this:
53 |
54 | * Most users probably won't need all the commands, so it'd be an overkill to
55 | define a minor mode consuming a lot of valuable keybindings
56 | * Many of the optimal keybindings are in the user space anyways (e.g. `C-c some-letter`)
57 | * Everyone has their own preferences when it comes to keybindings
58 |
59 | Here's the list of some suggested keybindings. Feel free to bind
60 | individual commands to whatever keybindings you prefer.
61 |
62 | Command | Suggested Keybinding(s) | Description
63 | ----------------------------------------------------|---------------------------------|------------------------
64 | `crux-open-with` | C-c o | Open the currently visited file with an external program.
65 | `crux-smart-kill-line` | C-k or Super-k | First kill to end of line, then kill the whole line.
66 | `crux-smart-open-line-above` | C-S-RET or Super-o | Insert an empty line above the current line and indent it properly.
67 | `crux-smart-open-line` | S-RET or M-o | Insert an empty line and indent it properly (as in most IDEs).
68 | `crux-cleanup-buffer-or-region` | C-c n | Fix indentation in buffer and strip whitespace.
69 | `crux-recentf-find-file` | C-c f or Super-r | Open recently visited file.
70 | `crux-recentf-find-directory` | C-c F | Open recently visited directory.
71 | `crux-view-url` | C-c u | Open a new buffer containing the contents of URL.
72 | `crux-eval-and-replace` | C-c e | Eval a bit of Emacs Lisp code and replace it with its result.
73 | `crux-transpose-windows` | C-x 4 t | Transpose the buffers between two windows.
74 | `crux-delete-file-and-buffer` | C-c D | Delete current file and buffer.
75 | `crux-copy-file-preserve-attributes` | C-c c | Copy current file with file attributes preserved
76 | `crux-duplicate-current-line-or-region` | C-c d | Duplicate the current line (or region).
77 | `crux-duplicate-and-comment-current-line-or-region` | C-c M-d | Duplicate and comment the current line (or region).
78 | `crux-rename-file-and-buffer` | C-c r | Rename the current buffer and its visiting file if any.
79 | `crux-visit-term-buffer` | C-c t | Open a terminal emulator (`ansi-term`).
80 | `crux-kill-other-buffers` | C-c k | Kill all open buffers except the one you're currently in.
81 | `crux-indent-defun` | C-M z | Indent the definition at point.
82 | `crux-indent-rigidly-and-copy-to-clipboard` | C-c TAB | Indent and copy region to clipboard
83 | `crux-find-user-init-file` | C-c I | Open user's init file.
84 | `crux-find-user-custom-file` | C-c , | Open user's custom file.
85 | `crux-find-shell-init-file` | C-c S | Open shell's init file.
86 | `crux-find-current-directory-dir-locals-file` | C-c D | Open current directory's `.dir-locals.el` file.
87 | `crux-top-join-line` | Super-j or C-^ | Join lines
88 | `crux-kill-whole-line` | Super-k | Kill whole line
89 | `crux-kill-line-backwards` | C-Backspace | Kill line backwards
90 | `crux-kill-and-join-forward` | C-S-Backspace or C-k | If at end of line, join with following; otherwise kill line.
91 | `crux-kill-buffer-truename ` | C-c P | Kill absolute path of file visited in current buffer.
92 | `crux-ispell-word-then-abbrev` | C-c i | Fix word using `ispell` and then save to `abbrev`.
93 | `crux-upcase-region` | C-x C-u | `upcase-region` when `transient-mark-mode` is on and region is active.
94 | `crux-downcase-region` | C-x C-l | `downcase-region` when `transient-mark-mode` is on and region is active.
95 | `crux-capitalize-region` | C-x M-c | `capitalize-region` when `transient-mark-mode` is on and region is active.
96 | `crux-other-window-or-switch-buffer` | M-o | Select other window, or switch to most recent buffer if only one windows.
97 | `crux-keyboard-quit-dwim` | C-g | `keyboard-quit` close the minibuffer or completions buffer even without focusing it.
98 |
99 | Here's how you'd bind some of the commands to keycombos:
100 |
101 | ```el
102 | (global-set-key [remap move-beginning-of-line] #'crux-move-beginning-of-line)
103 | (global-set-key (kbd "C-c o") #'crux-open-with)
104 | (global-set-key [(shift return)] #'crux-smart-open-line)
105 | (global-set-key (kbd "s-r") #'crux-recentf-find-file)
106 | (global-set-key (kbd "C-") #'crux-kill-line-backwards)
107 | (global-set-key [remap kill-whole-line] #'crux-kill-whole-line)
108 | (global-set-key [remap keyboard-quit] #'crux-keyboard-quit-dwim)
109 | ```
110 |
111 | For `crux-ispell-word-then-abbrev` to be most effective you'll also need to add this to your config:
112 |
113 | ```el
114 | (setq save-abbrevs 'silently)
115 | (setq-default abbrev-mode t)
116 | ```
117 |
118 | ## Using the bundled advices
119 |
120 | crux ships with some handy advises that can enhance the operation of existing commands.
121 |
122 | #### `(crux-with-region-or-buffer)` ####
123 |
124 | You can use `crux-with-region-or-buffer` to make a command acting
125 | normally on a region to operate on the entire buffer in the absence of
126 | a region. Here are a few examples you can stuff in your config:
127 |
128 | ```el
129 | (crux-with-region-or-buffer indent-region)
130 | (crux-with-region-or-buffer untabify)
131 | ```
132 |
133 | #### `(crux-with-region-or-line)` ####
134 |
135 | Likewise, you can use `crux-with-region-or-line` to make a command
136 | alternately act on the current line if the mark is not active:
137 |
138 | ```el
139 | (crux-with-region-or-line comment-or-uncomment-region)
140 | ```
141 |
142 | #### `(crux-with-region-or-sexp-or-line)` ####
143 |
144 | Similarly, `crux-with-region-or-sexp-or-line` makes a command that acts on the active region, or else
145 | the current list (or string), or finally the current line:
146 |
147 | ```el
148 | (crux-with-region-or-sexp-or-line kill-region)
149 | ```
150 |
151 | #### `(crux-with-region-or-point-to-eol)` ####
152 |
153 | Sometimes you might want to act on the point until the end of the
154 | current line, rather than the whole line, in the absence of a region:
155 |
156 | ``` el
157 | (crux-with-region-or-point-to-eol kill-ring-save)
158 | ```
159 |
160 | ## Minor modes
161 |
162 | #### `(crux-reopen-as-root-mode)` ####
163 |
164 | Crux provides a `crux-reopen-as-root` command for reopening a file as
165 | root. This global minor mode changes `find-file` so all root files are
166 | automatically opened as root.
167 |
168 | ## License
169 |
170 | Copyright © 2015-2025 Bozhidar Batsov and [contributors][].
171 |
172 | Distributed under the GNU General Public License; type C-h C-c to view it.
173 |
174 | [badge-license]: https://img.shields.io/badge/license-GPL_3-green.svg
175 | [melpa-badge]: http://melpa.org/packages/crux-badge.svg
176 | [melpa-stable-badge]: http://stable.melpa.org/packages/crux-badge.svg
177 | [melpa-package]: http://melpa.org/#/crux
178 | [melpa-stable-package]: http://stable.melpa.org/#/crux
179 | [COPYING]: http://www.gnu.org/copyleft/gpl.html
180 | [contributors]: https://github.com/bbatsov/crux/contributors
181 | [melpa]: http://melpa.org
182 | [melpa stable]: http://stable.melpa.org
183 |
--------------------------------------------------------------------------------
/crux.el:
--------------------------------------------------------------------------------
1 | ;;; crux.el --- A Collection of Ridiculously Useful eXtensions -*- lexical-binding: t -*-
2 | ;;
3 | ;; Copyright © 2015-2025 Bozhidar Batsov
4 | ;;
5 | ;; Author: Bozhidar Batsov
6 | ;; URL: https://github.com/bbatsov/crux
7 | ;; Version: 0.6.0-snapshot
8 | ;; Keywords: convenience
9 | ;; Package-Requires: ((emacs "26.1"))
10 |
11 | ;; This file is not part of GNU Emacs.
12 |
13 | ;;; Commentary:
14 |
15 | ;; A cornucopia of useful interactive commands to make your Emacs
16 | ;; experience more enjoyable.
17 |
18 | ;;; License:
19 |
20 | ;; This program is free software; you can redistribute it and/or
21 | ;; modify it under the terms of the GNU General Public License
22 | ;; as published by the Free Software Foundation; either version 3
23 | ;; of the License, or (at your option) any later version.
24 | ;;
25 | ;; This program is distributed in the hope that it will be useful,
26 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
27 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 | ;; GNU General Public License for more details.
29 | ;;
30 | ;; You should have received a copy of the GNU General Public License
31 | ;; along with GNU Emacs; see the file COPYING. If not, write to the
32 | ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
33 | ;; Boston, MA 02110-1301, USA.
34 |
35 | ;;; Code:
36 |
37 | (require 'thingatpt)
38 | (require 'seq)
39 | (require 'tramp)
40 | (require 'subr-x)
41 |
42 | (declare-function dired-get-file-for-visit "dired")
43 | (declare-function org-element-property "org-element")
44 | (declare-function org-element-context "org-element")
45 | (defvar recentf-list)
46 |
47 | (defgroup crux nil
48 | "crux configuration."
49 | :prefix "crux-"
50 | :group 'convenience)
51 |
52 | (defcustom crux-indent-sensitive-modes
53 | '(conf-mode coffee-mode haml-mode python-mode slim-mode yaml-mode)
54 | "Modes for which auto-indenting is suppressed."
55 | :type '(repeat symbol)
56 | :group 'crux)
57 |
58 | (defcustom crux-untabify-sensitive-modes
59 | '(makefile-bsdmake-mode)
60 | "Modes for which untabify is suppressed."
61 | :type '(repeat symbol)
62 | :group 'crux)
63 |
64 | (defcustom crux-line-start-regex-alist
65 | '((term-mode . "^[^#$%>\n]*[#$%>] ")
66 | (eshell-mode . "^[^$\n]*$ ")
67 | (org-mode . "^\\(\*\\|[[:space:]]*\\)* ")
68 | (default . "^[[:space:]]*"))
69 | "Alist of major modes and line starts.
70 |
71 | The key is a major mode. The value is a regular expression
72 | matching the characters to be skipped over. If no major mode is
73 | found, use the regex specified by the default key.
74 |
75 | Used by crux functions like `crux-move-beginning-of-line' to skip
76 | over whitespace, prompts, and markup at the beginning of the line."
77 | :type '(repeat (cons symbol regexp))
78 | :group 'crux)
79 |
80 |
81 | (defcustom crux-shell (getenv "SHELL")
82 | "The default shell to run with `crux-ansi-term'."
83 | :type 'string
84 | :group 'crux)
85 |
86 | (defcustom crux-shell-zsh-init-files
87 | '("$HOME/.zshrc" "$HOME/.zlogin" "$HOME/.zprofile" "$HOME/.zshenv"
88 | "$HOME/.zlogout" "/etc/zshenv" "/etc/zprofile" "/etc/zshrc" "/etc/zlogin"
89 | "/etc/zlogout" "$ZDOTDIR/.zshrc" "$ZDOTDIR/.zlogin" "$ZDOTDIR/.zprofile"
90 | "$ZDOTIR/.zshenv" "$ZDOTDIR/.zlogout")
91 | "The default init files of zsh."
92 | :type '(repeat string)
93 | :group 'crux)
94 |
95 | (defcustom crux-shell-bash-init-files
96 | '("$BASH_ENV" "$HOME/.bashrc" "$HOME/.bash_profile" "$HOME/.bash_login"
97 | "$HOME/.profile" "$HOME/.bash_logout" "/etc/bashrc" "/etc/bash_profile"
98 | "/etc/bash_login" "/etc/profile" "/etc/bash_logout")
99 | "The default init files of bash."
100 | :type '(repeat string)
101 | :group 'crux)
102 |
103 | (defcustom crux-shell-tcsh-init-files
104 | '("$HOME/.login" "$HOME/.cshrc" "$HOME/.tcshrc" "$HOME/.logout"
105 | "/etc/csh.cshrc" "/etc/csh.login" "/etc/csh.logout")
106 | "The default init files of tcsh."
107 | :type '(repeat string)
108 | :group 'crux)
109 |
110 | (defcustom crux-shell-fish-init-files
111 | '("$HOME/.config/fish/config.fish" "$XDG_CONFIG_HOME/fish/config.fish")
112 | "The default init files of fish."
113 | :type '(repeat string)
114 | :group 'crux)
115 |
116 | (defcustom crux-shell-ksh-init-files
117 | '("$HOME/.profile" "$ENV" "/etc/profile")
118 | "The default init files of ksh."
119 | :type '(repeat string)
120 | :group 'crux)
121 |
122 |
123 | (defcustom crux-term-func
124 | #'crux-ansi-term
125 | "The function used to start the term buffer if it's not already running.
126 |
127 | It will be called with a two arguments: the shell to start and the
128 | expected name of the shell buffer."
129 | :type 'function
130 | :group 'crux)
131 |
132 | (defcustom crux-shell-func
133 | #'crux-eshell
134 | "The function used to start the term buffer if it's not already running.
135 |
136 | It will be called with a two arguments: the shell to start and the
137 | expected name of the shell buffer."
138 | :type 'function
139 | :group 'crux)
140 |
141 | (defcustom crux-move-visually
142 | nil
143 | "Wheter move-related commands should take visual lines into account or not."
144 | :type 'boolean
145 | :group 'crux
146 | :package-version '(crux . "0.4.0"))
147 |
148 | (defun crux-ansi-term (buffer-name)
149 | "Use ansi-term for `crux-visit-term-buffer'"
150 | (ansi-term crux-shell buffer-name))
151 |
152 | (defvar eshell-buffer-name)
153 |
154 | (defun crux-eshell (buffer-name)
155 | "Use eshell for `crux-visit-term-buffer'"
156 | (let ((eshell-buffer-name (format "*%s*" buffer-name)))
157 | (eshell buffer-name)))
158 |
159 | (defun crux-shell (buffer-name)
160 | "Use eshell for `crux-visit-term-buffer'"
161 | (shell (format "*%s*" buffer-name)))
162 |
163 | ;;;###autoload
164 | (defun crux-open-with (arg)
165 | "Open visited file in default external program.
166 | When in dired mode, open file under the cursor.
167 |
168 | With a prefix ARG always prompt for command to use."
169 | (interactive "P")
170 | (let* ((current-file-name
171 | (if (derived-mode-p 'dired-mode)
172 | (dired-get-file-for-visit)
173 | buffer-file-name))
174 | (open (pcase system-type
175 | (`darwin "open")
176 | ((or `gnu `gnu/linux `gnu/kfreebsd) "xdg-open")))
177 | (program (if (or arg (not open))
178 | (read-shell-command "Open current file with: ")
179 | open)))
180 | (call-process program nil 0 nil current-file-name)))
181 |
182 | (defun crux-buffer-mode (buffer-or-name)
183 | "Retrieve the `major-mode' of BUFFER-OR-NAME."
184 | (with-current-buffer buffer-or-name
185 | major-mode))
186 |
187 | (defvar crux-term-buffer-name "ansi-term"
188 | "The default buffer name used by `crux-visit-term-buffer'.
189 | This variable can be set via .dir-locals.el to provide multi-term support.")
190 |
191 | (defvar crux-shell-buffer-name "shell"
192 | "The default buffer name used by `crux-visit-shell-buffer'.
193 | This variable can be set via .dir-locals.el to provide multi-term support.")
194 |
195 | (defun crux-start-or-switch-to (function buffer-name)
196 | "Invoke FUNCTION if there is no buffer with BUFFER-NAME.
197 | Otherwise switch to the buffer named BUFFER-NAME. Don't clobber
198 | the current buffer."
199 | (if (not (get-buffer buffer-name))
200 | (progn
201 | (split-window-sensibly (selected-window))
202 | (other-window 1)
203 | (funcall function))
204 | (switch-to-buffer-other-window buffer-name)))
205 |
206 | ;;;###autoload
207 | (defun crux-visit-term-buffer ()
208 | "Create or visit a terminal buffer.
209 | If the process in that buffer died, ask to restart."
210 | (interactive)
211 | (crux-start-or-switch-to (lambda ()
212 | (apply crux-term-func (list crux-term-buffer-name)))
213 | (format "*%s*" crux-term-buffer-name))
214 | (when (and (null (get-buffer-process (current-buffer)))
215 | (y-or-n-p "The process has died. Do you want to restart it? "))
216 | (kill-buffer-and-window)
217 | (crux-visit-term-buffer)))
218 |
219 | ;;;###autoload
220 | (defun crux-visit-shell-buffer ()
221 | "Create or visit a shell buffer.
222 | If the process in that buffer died, ask to restart."
223 | (interactive)
224 | (crux-start-or-switch-to (lambda ()
225 | (apply crux-shell-func (list crux-shell-buffer-name)))
226 | (format "*%s*" crux-shell-buffer-name))
227 | (when (and (null (get-buffer-process (current-buffer)))
228 | (not (derived-mode-p 'eshell-mode)) ; eshell has no process
229 | (y-or-n-p "The process has died. Do you want to restart it? "))
230 | (kill-buffer-and-window)
231 | (crux-visit-shell-buffer)))
232 |
233 | ;;;###autoload
234 | (defun crux-indent-rigidly-and-copy-to-clipboard (begin end arg)
235 | "Indent region between BEGIN and END by ARG columns and copy to clipboard."
236 | (interactive "r\nP")
237 | (let ((arg (or arg 4))
238 | (buffer (current-buffer)))
239 | (with-temp-buffer
240 | (insert-buffer-substring-no-properties buffer begin end)
241 | (indent-rigidly (point-min) (point-max) arg)
242 | (clipboard-kill-ring-save (point-min) (point-max)))))
243 |
244 | ;;;###autoload
245 | (defun crux-smart-open-line-above ()
246 | "Insert an empty line above the current line.
247 | Position the cursor at its beginning, according to the current mode."
248 | (interactive)
249 | (move-beginning-of-line nil)
250 | (insert "\n")
251 | (if electric-indent-inhibit
252 | ;; We can't use `indent-according-to-mode' in languages like Python,
253 | ;; as there are multiple possible indentations with different meanings.
254 | (let* ((indent-end (progn (crux-move-to-mode-line-start) (point)))
255 | (indent-start (progn (move-beginning-of-line nil) (point)))
256 | (indent-chars (buffer-substring indent-start indent-end)))
257 | (forward-line -1)
258 | ;; This new line should be indented with the same characters as
259 | ;; the current line.
260 | (insert indent-chars))
261 | ;; Just use the current major-mode's indent facility.
262 | (forward-line -1)
263 | (indent-according-to-mode)))
264 |
265 | ;;;###autoload
266 | (defun crux-smart-open-line (arg)
267 | "Insert an empty line after the current line.
268 | Position the cursor at its beginning, according to the current mode.
269 |
270 | With a prefix ARG open line above the current line."
271 | (interactive "P")
272 | (if arg
273 | (crux-smart-open-line-above)
274 | (move-end-of-line nil)
275 | (newline-and-indent)))
276 |
277 | ;;;###autoload
278 | (defun crux-smart-kill-line ()
279 | "Kill to the end of the line and kill whole line on the next call."
280 | (interactive)
281 | (let ((orig-point (point)))
282 | (move-end-of-line 1)
283 | (if (= orig-point (point))
284 | (crux-kill-whole-line)
285 | (goto-char orig-point)
286 | (kill-line))))
287 |
288 | ;;;###autoload
289 | (defun crux-top-join-line ()
290 | "Join the current line with the line beneath it."
291 | (interactive)
292 | (delete-indentation 1))
293 |
294 | ;;;###autoload
295 | (defun crux-kill-whole-line (&optional arg)
296 | "A simple wrapper around command `kill-whole-line' that respects indentation.
297 | Passes ARG to command `kill-whole-line' when provided."
298 | (interactive "p")
299 | (kill-whole-line arg)
300 | (crux-move-to-mode-line-start))
301 |
302 | ;;;###autoload
303 | (defun crux-kill-line-backwards ()
304 | "Kill line backwards and adjust the indentation."
305 | (interactive)
306 | (kill-line 0)
307 | (indent-according-to-mode))
308 |
309 | ;;;###autoload
310 | (defun crux-kill-and-join-forward (&optional arg)
311 | "If at end of line, join with following; otherwise kill line.
312 | Passes ARG to command `kill-line' when provided.
313 | Deletes whitespace at join."
314 | (interactive "P")
315 | (if (and (eolp) (not (bolp)))
316 | (delete-indentation 1)
317 | (kill-line arg)))
318 |
319 | ;;;###autoload
320 | (defun crux-move-to-mode-line-start ()
321 | "Move to the beginning, skipping mode specific line start regex."
322 | (interactive)
323 |
324 | (if crux-move-visually
325 | (beginning-of-visual-line nil)
326 | (move-beginning-of-line nil))
327 |
328 | (let ((line-start-regex (cdr (seq-find
329 | (lambda (e) (derived-mode-p (car e)))
330 | crux-line-start-regex-alist
331 | (assoc 'default crux-line-start-regex-alist)))))
332 | (search-forward-regexp line-start-regex (line-end-position) t)))
333 |
334 | ;;;###autoload
335 | (defun crux-move-beginning-of-line (arg)
336 | "Move point back to indentation of beginning of line.
337 |
338 | Move point to the first non-whitespace character on this line.
339 | If point is already there, move to the beginning of the line.
340 | Effectively toggle between the first non-whitespace character and
341 | the beginning of the line.
342 |
343 | If ARG is not nil or 1, move forward ARG - 1 lines first. If
344 | point reaches the beginning or end of the buffer, stop there."
345 | (interactive "^p")
346 | (setq arg (or arg 1))
347 |
348 | ;; Move lines first
349 | (when (/= arg 1)
350 | (let ((line-move-visual nil))
351 | (forward-line (1- arg))))
352 |
353 | (let ((orig-point (point)))
354 | (crux-move-to-mode-line-start)
355 | (when (= orig-point (point))
356 | (move-beginning-of-line 1))))
357 |
358 | ;;;###autoload
359 | (defun crux-indent-defun ()
360 | "Indent the current defun."
361 | (interactive)
362 | (save-excursion
363 | (mark-defun)
364 | (indent-region (region-beginning) (region-end))))
365 |
366 | (defun crux-get-positions-of-line-or-region ()
367 | "Return positions (beg . end) of the current line or region."
368 | (let (beg end)
369 | (if (and mark-active (> (point) (mark)))
370 | (exchange-point-and-mark))
371 | (setq beg (line-beginning-position))
372 | (if mark-active
373 | (exchange-point-and-mark))
374 | (setq end (line-end-position))
375 | (cons beg end)))
376 |
377 | ;;;###autoload
378 | (defun crux-duplicate-current-line-or-region (arg)
379 | "Duplicates the current line or region ARG times.
380 | If there's no region, the current line will be duplicated. However, if
381 | there's a region, all lines that region covers will be duplicated."
382 | (interactive "p")
383 | (pcase-let* ((origin (point))
384 | (`(,beg . ,end) (crux-get-positions-of-line-or-region))
385 | (region (buffer-substring-no-properties beg end)))
386 | (dotimes (_i arg)
387 | (goto-char end)
388 | (newline)
389 | (insert region)
390 | (setq end (point)))
391 | (goto-char (+ origin (* (length region) arg) arg))))
392 |
393 | ;;;###autoload
394 | (defun crux-duplicate-and-comment-current-line-or-region (arg)
395 | "Duplicates and comments the current line or region ARG times.
396 | If there's no region, the current line will be duplicated. However, if
397 | there's a region, all lines that region covers will be duplicated."
398 | (interactive "p")
399 | (pcase-let* ((origin (point))
400 | (`(,beg . ,end) (crux-get-positions-of-line-or-region))
401 | (region (buffer-substring-no-properties beg end)))
402 | (comment-or-uncomment-region beg end)
403 | (setq end (line-end-position))
404 | (dotimes (_ arg)
405 | (goto-char end)
406 | (newline)
407 | (insert region)
408 | (setq end (point)))
409 | (goto-char (+ origin (* (length region) arg) arg))))
410 |
411 | ;;;###autoload
412 | (defun crux-rename-file-and-buffer ()
413 | "Rename current buffer and if the buffer is visiting a file, rename it too."
414 | (interactive)
415 | (when-let* ((filename (buffer-file-name))
416 | (new-name (or (read-file-name "New name: " (file-name-directory filename) nil 'confirm)))
417 | (containing-dir (file-name-directory new-name)))
418 | ;; make sure the current buffer is saved and backed by some file
419 | (when (or (buffer-modified-p) (not (file-exists-p filename)))
420 | (if (y-or-n-p "Can't move file before saving it. Would you like to save it now?")
421 | (save-buffer)))
422 | (if (get-file-buffer new-name)
423 | (message "There already exists a buffer named %s" new-name)
424 | (progn
425 | (make-directory containing-dir t)
426 | (cond
427 | ((vc-backend filename)
428 | ;; vc-rename-file seems not able to cope with remote filenames?
429 | (let ((vc-filename (if (tramp-tramp-file-p filename) (tramp-file-local-name filename) filename))
430 | (vc-new-name (if (tramp-tramp-file-p new-name) (tramp-file-local-name filename) new-name)))
431 | (vc-rename-file vc-filename vc-new-name)))
432 | (t
433 | (rename-file filename new-name t)
434 | (set-visited-file-name new-name t t)))))))
435 |
436 | (defalias 'crux-rename-buffer-and-file #'crux-rename-file-and-buffer)
437 |
438 | ;;;###autoload
439 | (defun crux-delete-file-and-buffer ()
440 | "Kill the current buffer and deletes the file it is visiting."
441 | (interactive)
442 | (let ((filename (buffer-file-name)))
443 | (when filename
444 | (if (vc-backend filename)
445 | (vc-delete-file filename)
446 | (when (y-or-n-p (format "Are you sure you want to delete %s? " filename))
447 | (delete-file filename delete-by-moving-to-trash)
448 | (message "Deleted file %s" filename)
449 | (kill-buffer))))))
450 |
451 | (defalias 'crux-delete-buffer-and-file #'crux-delete-file-and-buffer)
452 |
453 | ;;;###autoload
454 | (defun crux-copy-file-preserve-attributes (visit)
455 | "Copy the current file-visiting buffer's file to a destination.
456 |
457 | This function prompts for the new file's location and copies it
458 | similar to cp -p. If the new location is a directory, and the
459 | directory does not exist, this function confirms with the user
460 | whether it should be created. A directory must end in a slash
461 | like `copy-file' expects. If the destination is a directory and
462 | already has a file named as the origin file, offers to
463 | overwrite.
464 |
465 | If the current buffer is not a file-visiting file or the
466 | destination is a non-existent directory but the user has elected
467 | to not created it, nothing will be done.
468 |
469 | When invoke with C-u, the newly created file will be visited.
470 | "
471 | (interactive "P")
472 | (when-let ((current-file (buffer-file-name)))
473 | (let* ((input-dest (expand-file-name (read-file-name "Copy file to: ")))
474 | (input-dest-is-dir? (or (file-directory-p input-dest)
475 | (string-match "/" input-dest (1- (length input-dest)))))
476 | (dest-file (if input-dest-is-dir?
477 | (expand-file-name (file-name-nondirectory current-file) input-dest)
478 | input-dest))
479 | (dest-dir (file-name-directory dest-file))
480 | (dest-dir-missing? (not (file-directory-p dest-dir)))
481 | (create-dir? (and dest-dir-missing?
482 | (y-or-n-p
483 | (format "%s is a non-existent directory, create it? " dest-dir))))
484 | (dest-file-exists? (file-regular-p dest-file))
485 | (overwrite-dest-file? (and dest-file-exists?
486 | (y-or-n-p
487 | (format "%s already exists, overwrite? " dest-file)))))
488 | (unless (or (and dest-dir-missing? (not create-dir?))
489 | (and dest-file-exists? (not overwrite-dest-file?)))
490 | (when (and dest-dir-missing? create-dir?)
491 | (make-directory dest-dir t))
492 | (copy-file current-file dest-file overwrite-dest-file? t t t)
493 | (message "Wrote %s" dest-file)
494 | (when visit
495 | (find-file-other-window dest-file))))))
496 |
497 | ;;;###autoload
498 | (defun crux-view-url ()
499 | "Open a new buffer containing the contents of URL."
500 | (interactive)
501 | (let* ((default (if (derived-mode-p 'org-mode)
502 | (org-element-property :raw-link (org-element-context))
503 | (thing-at-point-url-at-point)))
504 | (url (read-from-minibuffer "URL: " default)))
505 | (switch-to-buffer (url-retrieve-synchronously url))
506 | (rename-buffer url t)
507 | (goto-char (point-min))
508 | (re-search-forward "^$")
509 | (delete-region (point-min) (point))
510 | (delete-blank-lines)
511 | (set-auto-mode)))
512 |
513 | ;;;###autoload
514 | (defun crux-cleanup-buffer-or-region ()
515 | "Cleanup a region if selected, otherwise the whole buffer."
516 | (interactive)
517 | (unless (member major-mode crux-untabify-sensitive-modes)
518 | (call-interactively #'untabify))
519 | (unless (member major-mode crux-indent-sensitive-modes)
520 | (call-interactively #'indent-region))
521 | (whitespace-cleanup))
522 |
523 | ;;;###autoload
524 | (defun crux-eval-and-replace ()
525 | "Replace the preceding sexp with its value."
526 | (interactive)
527 | (let ((value (eval (elisp--preceding-sexp))))
528 | (backward-kill-sexp)
529 | (insert (format "%S" value))))
530 |
531 | ;;;###autoload
532 | (defun crux-recompile-init ()
533 | "Byte-compile all your dotfiles again."
534 | (interactive)
535 | (byte-recompile-directory user-emacs-directory 0))
536 |
537 | (defun crux-file-owner-uid (filename)
538 | "Return the UID of the FILENAME as an integer.
539 |
540 | See `file-attributes' for more info."
541 | (nth 2 (file-attributes filename 'integer)))
542 |
543 | (defun crux-file-owned-by-user-p (filename)
544 | "Return t if file FILENAME is owned by the currently logged in user."
545 | (equal (crux-file-owner-uid filename)
546 | (user-uid)))
547 |
548 | (defun crux-already-root-p ()
549 | (let ((remote-method (file-remote-p default-directory 'method))
550 | (remote-user (file-remote-p default-directory 'user)))
551 | (and remote-method
552 | (or (member remote-method '("sudo" "su" "ksu" "doas"))
553 | (string= remote-user "root")))))
554 |
555 | (defun crux-find-alternate-file-as-root (filename)
556 | "Wraps `find-alternate-file' with opening FILENAME as root."
557 | (let ((remote-method (file-remote-p default-directory 'method))
558 | (remote-host (file-remote-p default-directory 'host))
559 | (remote-localname (file-remote-p filename 'localname)))
560 | (find-alternate-file (format "/%s:root@%s:%s"
561 | (or remote-method (if (executable-find "doas")
562 | "doas"
563 | "sudo"))
564 | (or remote-host "localhost")
565 | (or remote-localname filename)))))
566 |
567 | ;;;###autoload
568 | (defun crux-sudo-edit (&optional arg)
569 | "Edit currently visited file as root.
570 |
571 | With a prefix ARG prompt for a file to visit.
572 | Will also prompt for a file to visit if current
573 | buffer is not visiting a file."
574 | (interactive "P")
575 | (if (or arg (not buffer-file-name))
576 | (let ((remote-method (file-remote-p default-directory 'method))
577 | (remote-host (file-remote-p default-directory 'host))
578 | (remote-localname (file-remote-p default-directory 'localname)))
579 | (find-file (format "/%s:root@%s:%s"
580 | (or remote-method (if (executable-find "doas")
581 | "doas"
582 | "sudo"))
583 | (or remote-host "localhost")
584 | (or remote-localname
585 | (read-file-name "Find file (as root): ")))))
586 |
587 | (if (crux-already-root-p)
588 | (message "Already editing this file as root.")
589 | (let ((place (point)))
590 | (crux-find-alternate-file-as-root buffer-file-name)
591 | (goto-char place)))))
592 |
593 | ;;;###autoload
594 | (defun crux-reopen-as-root ()
595 | "Find file as root if necessary.
596 |
597 | Meant to be used as `find-file-hook'.
598 | See also `crux-reopen-as-root-mode'."
599 | (unless (or (tramp-tramp-file-p buffer-file-name)
600 | (derived-mode-p 'dired-mode)
601 | (not (file-exists-p (file-name-directory buffer-file-name)))
602 | (file-writable-p buffer-file-name)
603 | (crux-file-owned-by-user-p buffer-file-name))
604 | (crux-find-alternate-file-as-root buffer-file-name)))
605 |
606 | ;;;###autoload
607 | (define-minor-mode crux-reopen-as-root-mode
608 | "Automatically reopen files as root if we can't write to them
609 | as the current user."
610 | :global t
611 | :group 'crux
612 | (if crux-reopen-as-root-mode
613 | (add-hook 'find-file-hook #'crux-reopen-as-root)
614 | (remove-hook 'find-file-hook #'crux-reopen-as-root)))
615 |
616 | ;;;###autoload
617 | (defun crux-insert-date ()
618 | "Insert a timestamp according to locale's date and time format."
619 | (interactive)
620 | (insert (format-time-string "%c" (current-time))))
621 |
622 | ;;;###autoload
623 | (defun crux-keyboard-quit-dwim ()
624 | "Do-What-I-Mean behaviour for a general `keyboard-quit'.
625 |
626 | The generic `keyboard-quit' does not do the expected thing when
627 | the minibuffer is open. Whereas we want it to close the
628 | minibuffer, even without explicitly focusing it.
629 |
630 | The DWIM behaviour of this command is as follows:
631 |
632 | - When the region is active, disable it.
633 | - When a minibuffer is open, but not focused, close the minibuffer.
634 | - When the Completions buffer is selected, close it.
635 | - In every other case use the regular `keyboard-quit'."
636 | (interactive)
637 | (cond
638 | ((region-active-p)
639 | (keyboard-quit))
640 | ((derived-mode-p 'completion-list-mode)
641 | (delete-completion-window))
642 | ((> (minibuffer-depth) 0)
643 | (abort-recursive-edit))
644 | (t
645 | (keyboard-quit))))
646 |
647 | ;;;###autoload
648 | (defun crux-recentf-find-file (&optional filter)
649 | "Find a recent file using `completing-read'.
650 | When optional argument FILTER is a function, it is used to
651 | transform recent files before completion."
652 | (interactive)
653 | (let* ((filter (if (functionp filter) filter #'abbreviate-file-name))
654 | (file (completing-read "Choose recent file: "
655 | (delete-dups (mapcar filter recentf-list))
656 | nil t)))
657 | (when file
658 | (find-file file))))
659 |
660 | (define-obsolete-function-alias 'crux-recentf-ido-find-file 'crux-recentf-find-file "0.4.0")
661 |
662 | ;;;###autoload
663 | (defun crux-recentf-find-directory ()
664 | "Find a recent directory using `completing-read'."
665 | (interactive)
666 | (crux-recentf-find-file (lambda (file) (abbreviate-file-name (file-name-directory file)))))
667 |
668 | ;; modified from https://www.emacswiki.org/emacs/TransposeWindows
669 | ;;;###autoload
670 | (defun crux-transpose-windows (arg)
671 | "Transpose the buffers shown in two windows.
672 | Prefix ARG determines if the current windows buffer is swapped
673 | with the next or previous window, and the number of
674 | transpositions to execute in sequence."
675 | (interactive "p")
676 | (let ((this-win (selected-window))
677 | (this-buffer (window-buffer)))
678 | (other-window arg)
679 | (set-window-buffer this-win (current-buffer))
680 | (set-window-buffer (selected-window) this-buffer)))
681 |
682 | (defalias 'crux-swap-windows 'crux-transpose-windows)
683 |
684 | ;;;###autoload
685 | (defun crux-switch-to-previous-buffer ()
686 | "Switch to previously open buffer.
687 | Repeated invocations toggle between the two most recently open buffers."
688 | (interactive)
689 | (switch-to-buffer (other-buffer (current-buffer) 1)))
690 |
691 | ;;;###autoload
692 | (defun crux-other-window-or-switch-buffer ()
693 | "Call `other-window' if more than one window is visible.
694 | Switch to most recent buffer otherwise."
695 | (interactive)
696 | (if (one-window-p)
697 | (switch-to-buffer nil)
698 | (other-window 1)))
699 |
700 | ;;;###autoload
701 | (defun crux-kill-other-buffers ()
702 | "Kill all buffers but the current one.
703 | Doesn't mess with special buffers."
704 | (interactive)
705 | (when (y-or-n-p "Are you sure you want to kill all buffers but the current one? ")
706 | (seq-each
707 | #'kill-buffer
708 | (delete (current-buffer) (seq-filter #'buffer-file-name (buffer-list))))))
709 |
710 | ;;;###autoload
711 | (defun crux-kill-buffer-truename ()
712 | "Kill absolute path of file visited in current buffer."
713 | (interactive)
714 | (if buffer-file-name
715 | (let ((truename (file-truename buffer-file-name)))
716 | (kill-new truename)
717 | (message "Added %s to kill ring." truename))
718 | (message "Buffer is not visiting a file.")))
719 |
720 | ;;;###autoload
721 | (defun crux-create-scratch-buffer ()
722 | "Create a new scratch buffer."
723 | (interactive)
724 | (let ((buf (generate-new-buffer "*scratch*")))
725 | (switch-to-buffer buf)
726 | (funcall initial-major-mode)))
727 |
728 | ;;;###autoload
729 | (defun crux-find-user-init-file ()
730 | "Edit the `user-init-file', in another window."
731 | (interactive)
732 | (find-file-other-window user-init-file))
733 |
734 | ;;;###autoload
735 | (defun crux-find-user-custom-file ()
736 | "Edit the `custom-file', in another window."
737 | (interactive)
738 | (if custom-file
739 | (find-file-other-window custom-file)
740 | (message "No custom file found.")))
741 |
742 | ;;;###autoload
743 | (defun crux-find-shell-init-file ()
744 | "Edit the shell init file in another window."
745 | (interactive)
746 | (let* ((shell (file-name-nondirectory (getenv "SHELL")))
747 | (shell-init-file (cond
748 | ((string= "zsh" shell) crux-shell-zsh-init-files)
749 | ((string= "bash" shell) crux-shell-bash-init-files)
750 | ((string= "tcsh" shell) crux-shell-tcsh-init-files)
751 | ((string= "fish" shell) crux-shell-fish-init-files)
752 | ((string-prefix-p "ksh" shell) crux-shell-ksh-init-files)
753 | (t (error "Unknown shell"))))
754 | (candidates (cl-remove-if-not 'file-exists-p (mapcar 'substitute-in-file-name shell-init-file))))
755 | (if (> (length candidates) 1)
756 | (find-file-other-window (completing-read "Choose shell init file: " candidates))
757 | (find-file-other-window (car candidates)))))
758 |
759 | ;;;###autoload
760 | (defun crux-find-current-directory-dir-locals-file (find-2)
761 | "Edit the `.dir-locals.el' file for the current buffer in another window.
762 | If prefix arg FIND-2 is set then edit the `.dir-locals-2.el' file instead
763 | of `.dir-locals.el'.
764 |
765 | Scans parent directories if the file does not exist in
766 | the default directory of the current buffer. If not found, create a new,
767 | empty buffer in the current buffer's default directory, or if there is no
768 | such directory, in the user's home directory."
769 | (interactive "P")
770 | (let* ((prefix (if (eq system-type 'ms-dos) "_" "."))
771 | (file (concat prefix (if find-2 "dir-locals-2" "dir-locals") ".el"))
772 | (starting-dir (or (when (and default-directory
773 | (file-readable-p default-directory))
774 | default-directory)
775 | (file-truename "~/")))
776 | (found-dir (or (locate-dominating-file starting-dir file) starting-dir))
777 | (found-file (concat found-dir file)))
778 | (find-file-other-window found-file)
779 | (if (file-exists-p found-file)
780 | (message "Editing existing file %s" found-file)
781 | (message "Editing new file %s" found-file))))
782 |
783 | ;;;###autoload
784 | (defun crux-upcase-region (beg end)
785 | "`upcase-region' when `transient-mark-mode' is on and region is active."
786 | (interactive "*r")
787 | (when (use-region-p)
788 | (upcase-region beg end)))
789 |
790 | ;;;###autoload
791 | (defun crux-downcase-region (beg end)
792 | "`downcase-region' when `transient-mark-mode' is on and region is active."
793 | (interactive "*r")
794 | (when (use-region-p)
795 | (downcase-region beg end)))
796 |
797 | ;;;###autoload
798 | (defun crux-capitalize-region (beg end)
799 | "`capitalize-region' when `transient-mark-mode' is on and region is active."
800 | (interactive "*r")
801 | (when (use-region-p)
802 | (capitalize-region beg end)))
803 |
804 | ;; http://endlessparentheses.com/ispell-and-abbrev-the-perfect-auto-correct.html
805 | ;;;###autoload
806 | (defun crux-ispell-word-then-abbrev (p)
807 | "Call `ispell-word', then create an abbrev for it.
808 | With prefix P, create local abbrev. Otherwise it will
809 | be global.
810 | If there's nothing wrong with the word at point, keep
811 | looking for a typo until the beginning of buffer. You can
812 | skip typos you don't want to fix with `SPC', and you can
813 | abort completely with `C-g'."
814 | (interactive "P")
815 | (let (bef aft)
816 | (save-excursion
817 | (while (if (setq bef (thing-at-point 'word))
818 | ;; Word was corrected or used quit.
819 | (if (ispell-word nil 'quiet)
820 | nil ; End the loop.
821 | ;; Also end if we reach `bob'.
822 | (not (bobp)))
823 | ;; If there's no word at point, keep looking
824 | ;; until `bob'.
825 | (not (bobp)))
826 | (backward-word))
827 | (setq aft (thing-at-point 'word)))
828 | (if (and aft bef (not (equal aft bef)))
829 | (let ((aft (downcase aft))
830 | (bef (downcase bef)))
831 | (define-abbrev
832 | (if p local-abbrev-table global-abbrev-table)
833 | bef aft)
834 | (message "\"%s\" now expands to \"%s\" %sally"
835 | bef aft (if p "loc" "glob")))
836 | (user-error "No typo at or before point"))))
837 |
838 | (defmacro crux-with-region-or-buffer (func)
839 | "When called with no active region, call FUNC on current buffer.
840 |
841 | Use to make commands like `indent-region' work on both the region
842 | and the entire buffer (in the absense of a region)."
843 | `(defadvice ,func (before with-region-or-buffer activate compile)
844 | (interactive
845 | (if mark-active
846 | (list (region-beginning) (region-end))
847 | (list (point-min) (point-max))))))
848 |
849 | (defmacro crux-with-region-or-line (func)
850 | "When called with no active region, call FUNC on current line."
851 | `(defadvice ,func (before with-region-or-line activate compile)
852 | (interactive
853 | (if mark-active
854 | (list (region-beginning) (region-end))
855 | (list (line-beginning-position) (line-beginning-position 2))))))
856 |
857 | (defmacro crux-with-region-or-sexp-or-line (func)
858 | "When called with no active region, call FUNC on current sexp/string, or line."
859 | `(defadvice ,func (before with-region-or-sexp-or-line activate compile)
860 | (interactive
861 | (cond
862 | (mark-active (list (region-beginning) (region-end)))
863 | ((in-string-p) (flatten-list (bounds-of-thing-at-point 'string)))
864 | ((thing-at-point 'list) (flatten-list (bounds-of-thing-at-point 'list)))
865 | (t (list (line-beginning-position) (line-beginning-position 2)))))))
866 |
867 | (defmacro crux-with-region-or-point-to-eol (func)
868 | "When called with no active region, call FUNC from the point to the end of line."
869 | `(defadvice ,func (before with-region-or-point-to-eol activate compile)
870 | (interactive
871 | (if mark-active
872 | (list (region-beginning) (region-end))
873 | (list (point) (line-end-position))))))
874 |
875 | (provide 'crux)
876 | ;;; crux.el ends here
877 |
--------------------------------------------------------------------------------