├── move-text.gif
├── .github
└── workflows
│ └── test.yml
├── README.md
├── move-text-tests.el
└── move-text.el
/move-text.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emacsfodder/move-text/HEAD/move-text.gif
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Move-Text CI Tests
2 |
3 | on:
4 | push:
5 | pull_request:
6 |
7 | jobs:
8 | test:
9 | runs-on: ubuntu-latest
10 | strategy:
11 | matrix:
12 | emacs_version:
13 | - 25.1
14 | - 25.2
15 | - 25.3
16 | - 26.1
17 | - 26.2
18 | - 26.3
19 | - 27.1
20 | - 27.2
21 | - 28.1
22 | - 28.2
23 | - 29.1
24 | - snapshot
25 | fail-fast: false
26 | steps:
27 | - uses: actions/checkout@v3
28 | - name: Set up Emacs
29 | uses: purcell/setup-emacs@master
30 | with:
31 | version: ${{ matrix.emacs_version }}
32 |
33 | - name: Test
34 | run: |
35 | emacs --batch \
36 | -eval "(setq load-prefer-newer t)" \
37 | -eval "(add-to-list 'load-path \".\")" \
38 | -l ert \
39 | -l move-text.el \
40 | -l move-text-tests.el \
41 | -f ert-run-tests-batch-and-exit
42 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > Hi all, Please consider donating to this or any of my many of opensource projects.
2 | >
3 | >
4 |
5 | [](https://github.com/emacsfodder/move-text/actions/workflows/test.yml)
6 | [](https://melpa.org/#/move-text)
7 | [](https://stable.melpa.org/#/move-text)
8 |
9 | # Move Text
10 |
11 | MoveText
12 | allows you to move the current line using M-up / M-down (or any other bindings you choose)
13 | if a region is marked, it will move the region instead.
14 |
15 | Using the prefix arg (C-u *number* or META *number*) will predetermine how many lines to move.
16 |
17 | Install from MELPA (or MELPA stable)
18 |
19 | ```
20 | M-x package-install move-text
21 | ```
22 |
23 | If you want to use the default bindings, add the following to .emacs
24 | anywhere after `(package-initialize)`:
25 |
26 | ```
27 | (move-text-default-bindings)
28 | ```
29 | This sets the keyboard shortcuts:
30 |
31 | - Meta-up `move-text-up` (line or active region)
32 | - Meta-down `move-text-down` (line or active region)
33 |
34 | ## Demonstration
35 |
36 | 
37 |
38 | ### Indent after moving...
39 |
40 | [@jbreeden](https://github.com/jbreeden) gave us this useful function advice to have Emacs re-indent the text in-and-around a text move.
41 |
42 | ```lisp
43 | (defun indent-region-advice (&rest ignored)
44 | (let ((deactivate deactivate-mark))
45 | (if (region-active-p)
46 | (indent-region (region-beginning) (region-end))
47 | (indent-region (line-beginning-position) (line-end-position)))
48 | (setq deactivate-mark deactivate)))
49 |
50 | (advice-add 'move-text-up :after 'indent-region-advice)
51 | (advice-add 'move-text-down :after 'indent-region-advice)
52 | ```
53 |
--------------------------------------------------------------------------------
/move-text-tests.el:
--------------------------------------------------------------------------------
1 | ;;; move-text-tests.el --- Tests for Move-Text -*- lexical-binding: t; -*-
2 | ;;
3 | ;; Created: September 07, 2022
4 |
5 | ;;; Code:
6 |
7 | (require 'move-text)
8 | (require 'ert)
9 |
10 | (defmacro should-on-temp-buffer (input-string expect-string &rest body)
11 | "INPUT-STRING processed by BODY forms in a temp buffer should equal EXPECT-STRING."
12 | (declare (indent 1) (debug t))
13 | `(should (string= ,expect-string
14 | (with-temp-buffer
15 | (insert ,input-string)
16 | (goto-char (point-min))
17 | ,@body
18 | (buffer-string)))))
19 |
20 | (let ((transient-mark-mode t))
21 |
22 | (ert-deftest move-line-down-test ()
23 | "Move text down by (1) one line, (2) by region."
24 | (should-on-temp-buffer
25 | "This is a test
26 | Line 2
27 | Line 3
28 | "
29 | "Line 2
30 | This is a test
31 | Line 3
32 | "
33 | (goto-char 0)
34 | (call-interactively #'move-text-down)))
35 |
36 | (ert-deftest move-region-down-test ()
37 | (should-on-temp-buffer
38 | "This is a test
39 | Line 2
40 | Line 3
41 | Line 4
42 | Line 5
43 | Line 6
44 | "
45 | "This is a test
46 | Line 2
47 | Line 4
48 | Line 3
49 | Line 5
50 | Line 6
51 | "
52 | (forward-line 2)
53 | (push-mark)
54 | (activate-mark)
55 | (forward-line)
56 | (message "Mark at %d - Point at %d" (mark) (point))
57 | (move-text-down (mark) (point) 1)))
58 |
59 | (ert-deftest move-line-up-test ()
60 | "Move text up by (1) one line, (2) by region."
61 | (should-on-temp-buffer
62 | "This is a test
63 | Line 2
64 | Line 3
65 | "
66 | "This is a test
67 | Line 3
68 | Line 2
69 | "
70 | (forward-line 2)
71 | (call-interactively #'move-text-up)))
72 |
73 | (ert-deftest move-region-up-test ()
74 | (should-on-temp-buffer
75 | "This is a test
76 | Line 2
77 | Line 3
78 | Line 4
79 | Line 5
80 | Line 6
81 | "
82 | "Line 2
83 | Line 3
84 | This is a test
85 | Line 4
86 | Line 5
87 | Line 6
88 | "
89 | (goto-char 0)
90 | (forward-line)
91 | (push-mark)
92 | (activate-mark)
93 | (forward-line 2)
94 | (move-text-up (mark) (point) 1))))
95 |
96 | ;;; move-text-tests.el ends here
97 |
--------------------------------------------------------------------------------
/move-text.el:
--------------------------------------------------------------------------------
1 | ;;; move-text.el --- Move current line or region with M-up or M-down. -*- lexical-binding: t; -*-
2 |
3 | ;; filename: move-text.el
4 | ;; Description: Move current line or region with M-up or M-down.
5 | ;; Author: Jason Milkins
6 | ;; Keywords: edit
7 | ;; Url: https://github.com/emacsfodder/move-text
8 | ;; Compatibility: GNU Emacs 25.1
9 | ;; Version: 2.0.10
10 | ;;
11 | ;;; This file is NOT part of GNU Emacs
12 |
13 | ;;; License
14 | ;;
15 | ;; This program is free software; you can redistribute it and/or modify
16 | ;; it under the terms of the GNU General Public License as published by
17 | ;; the Free Software Foundation; either version 3, or (at your option)
18 | ;; any later version.
19 |
20 | ;; This program is distributed in the hope that it will be useful,
21 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
22 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 | ;; GNU General Public License for more details.
24 |
25 | ;; You should have received a copy of the GNU General Public License
26 | ;; along with this program; see the file COPYING. If not, write to
27 | ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth
28 | ;; Floor, Boston, MA 02110-1301, USA.
29 |
30 | ;;; Commentary:
31 | ;;
32 | ;; MoveText 2.0.0 is a re-write of the old move-text and compatible with >= Emacs 25.1
33 | ;;
34 | ;; It allows you to move the current line using M-up / M-down if a
35 | ;; region is marked, it will move the region instead.
36 | ;;
37 | ;; Using the prefix (C-u *number* or META *number*) you can predefine how
38 | ;; many lines move-text will travel.
39 | ;;
40 |
41 | ;;; Installation:
42 | ;;
43 | ;; Put move-text.el to your load-path.
44 | ;; The load-path is usually ~/elisp/.
45 | ;; It's set in your ~/.emacs like this:
46 | ;; (add-to-list 'load-path (expand-file-name "~/elisp"))
47 | ;;
48 | ;; And the following to your ~/.emacs startup file.
49 | ;;
50 | ;; (require 'move-text)
51 | ;; (move-text-default-bindings)
52 |
53 | ;;; Acknowledgements:
54 | ;;
55 | ;; Original v1.x was a Feature extracted from basic-edit-toolkit.el - by Andy Stewart (LazyCat)
56 | ;;
57 |
58 | ;;; Code:
59 | (require 'cl-lib)
60 |
61 | (defun move-text-get-region-and-prefix ()
62 | "Get the region and prefix for the `interactive' macro, without aborting.
63 |
64 | Note: `region-beginning' and `region-end' are the reason why an
65 | `interactive' macro with \"r\" will blow up with the error:
66 |
67 | \"The mark is not set now, so there is no region\"
68 |
69 | We check with `use-region-p' to avoid calling
70 | them when there's no region or it is not appropriate
71 | to act on it.
72 |
73 | We use `prefix-numeric-value' to return a number.
74 | "
75 | (list
76 | (when (use-region-p) (region-beginning)) ;; otherwise nil
77 | (when (use-region-p) (region-end))
78 | (prefix-numeric-value current-prefix-arg)))
79 |
80 | ;;;###autoload
81 | (defun move-text--total-lines ()
82 | "Convenience function to get the total lines in the buffer / or narrowed buffer."
83 | (line-number-at-pos (point-max)))
84 |
85 | ;;;###autoload
86 | (defun move-text--at-first-line-p ()
87 | "Predicate, is the point at the first line?"
88 | (= (line-number-at-pos) 1))
89 |
90 | ;;;###autoload
91 | (defun move-text--at-penultimate-line-p ()
92 | "Predicate, is the point at the penultimate line?"
93 | (= (line-number-at-pos) (1- (move-text--total-lines))))
94 |
95 | ;; save-mark-and-excursion in Emacs 25 works like save-excursion did before
96 | (eval-when-compile
97 | (when (< emacs-major-version 25)
98 | (defmacro save-mark-and-excursion (&rest body)
99 | `(save-excursion ,@body))))
100 |
101 | ;;;###autoload
102 | (defun move-text--last-line-is-just-newline ()
103 | "Predicate, is last line just a newline?"
104 | (save-mark-and-excursion
105 | (goto-char (point-max))
106 | (beginning-of-line)
107 | (= (point-max) (point))))
108 |
109 | ;;;###autoload
110 | (defun move-text--at-last-line-p ()
111 | "Predicate, is the point at the last line?"
112 | (= (line-number-at-pos) (move-text--total-lines)))
113 |
114 | ;;;###autoload
115 | (defun move-text-line-up ()
116 | "Move the current line up."
117 | (interactive)
118 | (if (move-text--at-last-line-p)
119 | (let ((target-point))
120 | (kill-whole-line)
121 | (forward-line -1)
122 | (beginning-of-line)
123 | (setq target-point (point))
124 | (yank)
125 | (unless (looking-at "\n")
126 | (newline))
127 | (goto-char target-point))
128 | (let ((col (current-column)))
129 | (progn (transpose-lines 1)
130 | (forward-line -2)
131 | (move-to-column col)))))
132 |
133 | ;;;###autoload
134 | (defun move-text-line-down ()
135 | "Move the current line down."
136 | (interactive)
137 | (unless (or
138 | (move-text--at-last-line-p)
139 | (and
140 | (move-text--last-line-is-just-newline)
141 | (move-text--at-penultimate-line-p)))
142 | (let ((col (current-column)))
143 | (forward-line 1)
144 | (transpose-lines 1)
145 | (forward-line -1)
146 | (move-to-column col))))
147 |
148 | ;;;###autoload
149 | (defun move-text-region (start end n)
150 | "Move the current region (START END) up or down by N lines."
151 | (interactive (move-text-get-region-and-prefix))
152 | (let ((line-text (delete-and-extract-region start end)))
153 | (forward-line n)
154 | (let ((start (point)))
155 | (insert line-text)
156 | (setq deactivate-mark nil)
157 | (set-mark start))))
158 |
159 | ;;;###autoload
160 | (defun move-text-region-up (start end n)
161 | "Move the current region (START END) up by N lines."
162 | (interactive (move-text-get-region-and-prefix))
163 | (move-text-region start end (- n)))
164 |
165 | ;;;###autoload
166 | (defun move-text-region-down (start end n)
167 | "Move the current region (START END) down by N lines."
168 | (interactive (move-text-get-region-and-prefix))
169 | (move-text-region start end n))
170 |
171 | ;;;###autoload
172 | (defun move-text-up (start end n)
173 | "Move the line or region (START END) up by N lines."
174 | (interactive (move-text-get-region-and-prefix))
175 | (if (not (move-text--at-first-line-p))
176 | (if (region-active-p)
177 | (move-text-region-up start end n)
178 | (cl-loop repeat n do (move-text-line-up)))))
179 |
180 | ;;;###autoload
181 | (defun move-text-down (start end n)
182 | "Move the line or region (START END) down by N lines."
183 | (interactive (move-text-get-region-and-prefix))
184 | (if (region-active-p)
185 | (move-text-region-down start end n)
186 | (cl-loop repeat n do (move-text-line-down))))
187 |
188 | ;;;###autoload
189 | (defun move-text-default-bindings ()
190 | "Bind `move-text-up' and `move-text-down' to M-up & M-down."
191 | (interactive)
192 | (global-set-key [M-down] 'move-text-down)
193 | (global-set-key [M-up] 'move-text-up))
194 |
195 | (provide 'move-text)
196 |
197 | ;;; move-text.el ends here
198 |
--------------------------------------------------------------------------------