├── .github ├── dependabot.yml └── workflows │ └── ci.yaml ├── .gitignore ├── README.md ├── fix-word.el ├── flake.lock └── flake.nix /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | types: 8 | - opened 9 | - synchronize 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: cachix/install-nix-action@v31 16 | - run: nix build 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *-autoloads.el 2 | *.elc 3 | *~ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fix Word 2 | 3 | [![License GPL 3](https://img.shields.io/badge/license-GPL_3-green.svg)](http://www.gnu.org/licenses/gpl-3.0.txt) 4 | [![MELPA](https://melpa.org/packages/fix-word-badge.svg)](https://melpa.org/#/fix-word) 5 | [![CI](https://github.com/mrkkrp/fix-word/actions/workflows/ci.yaml/badge.svg)](https://github.com/mrkkrp/fix-word/actions/workflows/ci.yaml) 6 | 7 | This is a package that allows us to transform words intelligently. It 8 | provides the function `fix-word` that lifts functions that do string 9 | transformation into commands with interesting behavior. There are also some 10 | built-in commands built on top of `fix-word`. 11 | 12 | ## Installation 13 | 14 | The package is available via MELPA, so you can just type `M-x 15 | package-install RET fix-word RET`. 16 | 17 | If you would like to install the package manually, download or clone it and 18 | put on Emacs' `load-path`. Then you can require it in your init file like 19 | this: 20 | 21 | ```emacs-lisp 22 | (require 'fix-word) 23 | ``` 24 | 25 | ## API description 26 | 27 | ``` 28 | fix-word fnc 29 | ``` 30 | 31 | Lift the function `fnc` into a command that operates on words and regions. 32 | 33 | The following behaviors are implemented: 34 | 35 | 1. If the point is placed outside of a word, apply `fnc` to the previous 36 | word. When the command is invoked repeatedly, every its invocation 37 | transforms one more word moving from right to left. For example 38 | (upcasing, `^` shows the position of the point): 39 | 40 | ``` 41 | The quick brown fox jumps over the lazy dog.^ 42 | The quick brown fox jumps over the lazy DOG.^ 43 | The quick brown fox jumps over the LAZY DOG.^ 44 | The quick brown fox jumps over THE LAZY DOG.^ 45 | ``` 46 | 47 | The point doesn't move, this allows us to fix recently entered words and 48 | continue typing. 49 | 50 | 2. If the point is placed inside of a word, the entire word is transformed. 51 | The point is moved to the first character of the next word. This allows 52 | us to transform several words by invoking the command repeatedly. 53 | 54 | ``` 55 | ^The quick brown fox jumps over the lazy dog. 56 | THE ^quick brown fox jumps over the lazy dog. 57 | THE QUICK ^brown fox jumps over the lazy dog. 58 | THE QUICK BROWN ^fox jumps over the lazy dog. 59 | ``` 60 | 61 | 3. If there is an active region, all words in that region are transformed. 62 | 63 | Use `fix-word` to create new commands like this: 64 | 65 | ```emacs-lisp 66 | (defalias 'command-name (fix-word #'upcase) 67 | "Description of the command.") 68 | ``` 69 | 70 | There is also a macro that defines such commands for you: 71 | `fix-word-define-command`. 72 | 73 | ---- 74 | 75 | ``` 76 | fix-word-define-command name fnc &optional doc 77 | ``` 78 | 79 | Define a `fix-word`-based command named `name`. `fnc` is the processing 80 | function and `doc` is the documentation string. 81 | 82 | ## Built-in commands 83 | 84 | The default commands to upcase/downcase/capitalize words are not convenient, 85 | for the following reasons: 86 | 87 | 1. There are three different commands for upcaseing, for example. The user 88 | needs to remember the three commands, their key bindings, and when to use 89 | each of them. There should be one command per action: one for upcasing, 90 | one for downcasing, and one for capitalizing. 91 | 92 | 2. The commands on regions don't have dedicated key bindings and are 93 | disabled by default. 94 | 95 | 3. The commands like `upcase-word` depend on the position of pointer inside 96 | of the word, so that the result of upcasing `"fo^o"`is `"foO"`. This 97 | packages assumes that you want `"FOO"`. 98 | 99 | 4. One needs to use arguments for commands like `upcase-word` to make them 100 | correct the words that one has just written and only one word can be 101 | adjusted in this way. 102 | 103 | Here are the commands that try to fix these flaws: 104 | 105 | * `fix-word-upcase` 106 | * `fix-word-downcase` 107 | * `fix-word-capitalize` 108 | 109 | I propose replacing of the built-ins with these new commands: 110 | 111 | ```emacs-lisp 112 | (global-set-key (kbd "M-u") #'fix-word-upcase) 113 | (global-set-key (kbd "M-l") #'fix-word-downcase) 114 | (global-set-key (kbd "M-c") #'fix-word-capitalize) 115 | ``` 116 | 117 | ## License 118 | 119 | Copyright © 2015–present Mark Karpov 120 | 121 | Distributed under GNU GPL, version 3. 122 | -------------------------------------------------------------------------------- /fix-word.el: -------------------------------------------------------------------------------- 1 | ;;; fix-word.el --- Convenient word transformation -*- lexical-binding: t; -*- 2 | ;; 3 | ;; Copyright © 2015–present Mark Karpov 4 | ;; 5 | ;; Author: Mark Karpov 6 | ;; URL: https://github.com/mrkkrp/fix-word 7 | ;; Version: 0.2.0 8 | ;; Package-Requires: ((emacs "24.1") (cl-lib "0.5")) 9 | ;; Keywords: word, convenience 10 | ;; 11 | ;; This file is not part of GNU Emacs. 12 | ;; 13 | ;; This program is free software: you can redistribute it and/or modify it 14 | ;; under the terms of the GNU General Public License as published by the 15 | ;; Free Software Foundation, either version 3 of the License, or (at your 16 | ;; option) any later version. 17 | ;; 18 | ;; This program is distributed in the hope that it will be useful, but 19 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of 20 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 21 | ;; Public License for more details. 22 | ;; 23 | ;; You should have received a copy of the GNU General Public License along 24 | ;; with this program. If not, see . 25 | 26 | ;;; Commentary: 27 | 28 | ;; This is a package that allows us to transform words intelligently. It 29 | ;; provides the function `fix-word' that lifts functions that do string 30 | ;; transformation into commands with interesting behavior. There are also 31 | ;; some built-in commands built on top of `fix-word'. 32 | 33 | ;;; Code: 34 | 35 | (require 'cl-lib) 36 | 37 | (defgroup fix-word nil 38 | "Convenient word transformation." 39 | :group 'convenience 40 | :tag "Fix word" 41 | :prefix "fix-word-" 42 | :link '(url-link :tag "GitHub" "https://github.com/mrkkrp/fix-word")) 43 | 44 | (defcustom fix-word-bounds-of-thing-function 45 | #'bounds-of-thing-at-point 46 | "Function to get the boundaries of a thing at point. 47 | 48 | This variable lets you customize the way this package determines 49 | the boundaries of a word." 50 | :group 'fix-word 51 | :type 'function) 52 | 53 | (defcustom fix-word-thing 'word 54 | "The default transformation target of fix-word. 55 | 56 | This should be a symbol that can be passed as the argument to 57 | `bounds-of-thing-at-point' or its compatible function." 58 | :group 'fix-word 59 | :type 'symbol) 60 | 61 | ;;;###autoload 62 | (defun fix-word (fnc) 63 | "Lift function FNC into command that operates on words and regions. 64 | 65 | The following behaviors are implemented: 66 | 67 | If the point is placed outside of a word, apply FNC to the 68 | previous word. When the command is invoked repeatedly, every its 69 | invocation transforms one more word moving from right to left. 70 | For example (upcasing, ^ shows the position of the point): 71 | 72 | The quick brown fox jumps over the lazy dog.^ 73 | The quick brown fox jumps over the lazy DOG.^ 74 | The quick brown fox jumps over the LAZY DOG.^ 75 | The quick brown fox jumps over THE LAZY DOG.^ 76 | 77 | The point doesn't move, this allows us to fix recently entered 78 | words and continue typing. 79 | 80 | If the point is placed inside a word, the entire word is 81 | transformed. The point is moved to the first character of the 82 | next word. This allows us to transform several words by invoking 83 | the command repeatedly. 84 | 85 | ^The quick brown fox jumps over the lazy dog. 86 | THE ^quick brown fox jumps over the lazy dog. 87 | THE QUICK ^brown fox jumps over the lazy dog. 88 | THE QUICK BROWN ^fox jumps over the lazy dog. 89 | 90 | If there is an active region, all words in that region are 91 | transformed. 92 | 93 | Use `fix-word' to create new commands like this: 94 | 95 | \(defalias 'command-name (fix-word #'upcase) 96 | \"Description of the command.\") 97 | 98 | There is also a macro that defines such commands for you: 99 | `fix-word-define-command'." 100 | (lambda (&optional arg) 101 | (interactive "p") 102 | (if (region-active-p) 103 | (fix-word--fix-region fnc) 104 | (funcall 105 | (if (looking-at "\\w+\\>") 106 | #'fix-word--fix-and-move 107 | #'fix-word--fix-quickly) 108 | fnc arg)))) 109 | 110 | (defun fix-word--fix-region (fnc) 111 | "Transform the active region with function FNC." 112 | (let* ((from (point)) 113 | (to (mark)) 114 | (str (buffer-substring-no-properties from to))) 115 | (delete-region from to) 116 | (insert (funcall fnc str)) 117 | (goto-char from))) 118 | 119 | (defun fix-word--fix-and-move (fnc &optional arg) 120 | "Transform the current word with function FNC and move to the next word. 121 | 122 | If the argument ARG is supplied, repeat the operation ARG times." 123 | (dotimes (_ (or arg 1)) 124 | (fix-word--transform-word fnc) 125 | (forward-word 2) 126 | (backward-word))) 127 | 128 | (defvar fix-word--quick-fix-times 1 129 | "How many times `fix-word--fix-quickly' has been invoked consequently.") 130 | 131 | (defun fix-word--fix-quickly (fnc &optional arg) 132 | "Transform the previous word with the function FNC. 133 | 134 | If this function is invoked repeatedly, transform more words 135 | moving from right to left. If the argument ARG is supplied, 136 | repeat the operation ARG times." 137 | (interactive) 138 | (let* ((origin (point)) 139 | (i (if (eq last-command this-command) 140 | (setq fix-word--quick-fix-times 141 | (1+ fix-word--quick-fix-times)) 142 | (setq fix-word--quick-fix-times 1)))) 143 | (backward-word i) 144 | (fix-word--transform-word fnc) 145 | (when arg 146 | (dotimes (_ (1- arg)) 147 | (backward-word) 148 | (fix-word--transform-word fnc)) 149 | (setq fix-word--quick-fix-times 150 | (+ fix-word--quick-fix-times arg -1))) 151 | (goto-char origin))) 152 | 153 | (defun fix-word--transform-word (fnc) 154 | "Transform the word at the point with the function FNC." 155 | (let ((bounds (funcall (or fix-word-bounds-of-thing-function 156 | 'bounds-of-thing-at-point) 157 | (or fix-word-thing 'word)))) 158 | (when bounds 159 | (cl-destructuring-bind (from . to) bounds 160 | (let ((origin (point)) 161 | (str (buffer-substring-no-properties from to))) 162 | (delete-region from to) 163 | (insert (funcall fnc str)) 164 | (goto-char origin)))))) 165 | 166 | ;;;###autoload 167 | (defmacro fix-word-define-command (name fnc &optional doc) 168 | "Define a `fix-word'-based command named NAME. 169 | 170 | FNC is the processing function and DOC is the documentation string." 171 | (declare (indent defun)) 172 | `(defalias ',name (fix-word ,fnc) 173 | ,(concat (or doc "Name of the command should be self-explanatory.") 174 | "\n\nArgument ARG, if given, specifies how many times to perform the command. 175 | \nThis command is `fix-word'-based. See its description for more information."))) 176 | 177 | ;; Here are some default commands implemented with `fix-word'. 178 | 179 | ;;;###autoload 180 | (fix-word-define-command fix-word-upcase #'upcase 181 | "Upcase words intelligently.") 182 | ;;;###autoload 183 | (fix-word-define-command fix-word-downcase #'downcase 184 | "Downcase words intelligently.") 185 | ;;;###autoload 186 | (fix-word-define-command fix-word-capitalize #'capitalize 187 | "Capitalize words intelligently.") 188 | 189 | (provide 'fix-word) 190 | 191 | ;;; fix-word.el ends here 192 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "emacs-package-flake": { 4 | "inputs": { 5 | "flake-utils": "flake-utils", 6 | "nixpkgs": "nixpkgs" 7 | }, 8 | "locked": { 9 | "lastModified": 1686061605, 10 | "narHash": "sha256-P9zhW//ov8RsCpp4YWgsPN/jXfJbNJkYci5ohCCtRi0=", 11 | "owner": "mrkkrp", 12 | "repo": "emacs-package-flake", 13 | "rev": "16560243f855e9e918a1026768d53a78d047ef43", 14 | "type": "github" 15 | }, 16 | "original": { 17 | "owner": "mrkkrp", 18 | "repo": "emacs-package-flake", 19 | "type": "github" 20 | } 21 | }, 22 | "flake-utils": { 23 | "inputs": { 24 | "systems": "systems" 25 | }, 26 | "locked": { 27 | "lastModified": 1685518550, 28 | "narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=", 29 | "owner": "numtide", 30 | "repo": "flake-utils", 31 | "rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef", 32 | "type": "github" 33 | }, 34 | "original": { 35 | "owner": "numtide", 36 | "repo": "flake-utils", 37 | "type": "github" 38 | } 39 | }, 40 | "nixpkgs": { 41 | "locked": { 42 | "lastModified": 1685931219, 43 | "narHash": "sha256-8EWeOZ6LKQfgAjB/USffUSELPRjw88A+xTcXnOUvO5M=", 44 | "owner": "NixOS", 45 | "repo": "nixpkgs", 46 | "rev": "7409480d5c8584a1a83c422530419efe4afb0d19", 47 | "type": "github" 48 | }, 49 | "original": { 50 | "id": "nixpkgs", 51 | "ref": "nixos-unstable", 52 | "type": "indirect" 53 | } 54 | }, 55 | "root": { 56 | "inputs": { 57 | "emacs-package-flake": "emacs-package-flake" 58 | } 59 | }, 60 | "systems": { 61 | "locked": { 62 | "lastModified": 1681028828, 63 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 64 | "owner": "nix-systems", 65 | "repo": "default", 66 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 67 | "type": "github" 68 | }, 69 | "original": { 70 | "owner": "nix-systems", 71 | "repo": "default", 72 | "type": "github" 73 | } 74 | } 75 | }, 76 | "root": "root", 77 | "version": 7 78 | } 79 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | emacs-package-flake.url = "github:mrkkrp/emacs-package-flake"; 4 | }; 5 | outputs = { self, emacs-package-flake }: 6 | emacs-package-flake.lib.mkOutputs { 7 | name = "fix-word"; 8 | srcDir = ./.; 9 | }; 10 | } 11 | --------------------------------------------------------------------------------