├── .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 | --------------------------------------------------------------------------------