al-text-cmd.el 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. ;;; al-text-cmd.el --- Various interactive commands for working with text
  2. ;; Copyright © 2013–2016, 2018 Alex Kost
  3. ;; This program is free software; you can redistribute it and/or modify
  4. ;; it under the terms of the GNU General Public License as published by
  5. ;; the Free Software Foundation, either version 3 of the License, or
  6. ;; (at your option) any later version.
  7. ;;
  8. ;; This program is distributed in the hope that it will be useful,
  9. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. ;; GNU General Public License for more details.
  12. ;;
  13. ;; You should have received a copy of the GNU General Public License
  14. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. ;;; Code:
  16. ;;; Searching and replacing
  17. ;;;###autoload
  18. (defun al/re-search-forward (regexp)
  19. "The function is similar to `re-search-forward' except it continues
  20. the search from the beginning of the buffer if it did not succeed."
  21. (interactive "sRegExp search: ")
  22. (let ((pos (point)))
  23. (or (re-search-forward regexp nil t)
  24. (progn
  25. (goto-char (point-min))
  26. (re-search-forward regexp pos t))
  27. (progn
  28. (goto-char pos)
  29. nil))))
  30. ;;; Editing
  31. (defvar al/delimiter
  32. "\f\n"
  33. "String for separating text in elisp code.")
  34. ;;;###autoload
  35. (defun al/insert-delimiter ()
  36. "Insert `al/delimiter' at point."
  37. (interactive)
  38. (insert al/delimiter))
  39. (declare-function org-read-date "org" t)
  40. ;;;###autoload
  41. (defun al/insert-date (&optional arg)
  42. "Insert date at point.
  43. If ARG is nil, use current date.
  44. If ARG is non-nil, prompt for a date."
  45. (interactive "P")
  46. (insert (if arg
  47. (progn (require 'org)
  48. (org-read-date))
  49. (format-time-string "%Y-%m-%d"))))
  50. ;;;###autoload
  51. (defun al/insert-clipboard ()
  52. "Insert the clipboard contents.
  53. It doesn't destroy what you paste with \\[yank]."
  54. (interactive)
  55. (let ((clp (gui--selection-value-internal 'CLIPBOARD)))
  56. (if clp
  57. (insert clp)
  58. (message "Clipboard is empty."))))
  59. (defun al/yank-or-pop (n)
  60. "Replace just-yanked text with the N-th kill.
  61. If last command is not `yank', call `yank' N times."
  62. (if (eq last-command 'yank)
  63. (yank-pop n)
  64. (dotimes (i (abs n)) (yank))))
  65. ;;;###autoload
  66. (defun al/yank-or-prev (arg)
  67. "Replace just-yanked text with the previous kill.
  68. See `al/yank-or-pop' for details."
  69. (interactive "p")
  70. (al/yank-or-pop arg))
  71. ;;;###autoload
  72. (defun al/yank-or-next (arg)
  73. "Replace just-yanked text with the next kill.
  74. See `al/yank-or-pop' for details."
  75. (interactive "p")
  76. (al/yank-or-pop (- arg)))
  77. ;;;###autoload
  78. (defun al/flush-blank-lines (start end)
  79. "Delete all empty lines in selected region."
  80. (interactive "r")
  81. (flush-lines "^\\s-*$" start end nil))
  82. ;;;###autoload
  83. (defun al/delete-blank-lines ()
  84. "Delete blank lines.
  85. If region is active, call `al/flush-blank-lines',
  86. otherwise call `delete-blank-lines'."
  87. (interactive)
  88. (if (region-active-p)
  89. (al/flush-blank-lines (region-beginning) (region-end))
  90. (delete-blank-lines)))
  91. ;;;###autoload
  92. (defun al/kill-line (arg)
  93. "Similar to `kill-line' but kill including its terminating newline."
  94. (interactive "p")
  95. (kill-region (point)
  96. (progn (forward-visible-line arg) (point))))
  97. ;;;###autoload
  98. (defun al/backward-kill-line (arg)
  99. "Kill line to its beginning.
  100. With prefix argument ARG, kill that many lines backward including current."
  101. (interactive "p")
  102. (kill-region (point)
  103. (progn (forward-visible-line (- 1 arg)) (point))))
  104. ;;;###autoload
  105. (defun al/save-line (arg)
  106. "Similar to `kill-line' but save in a kill ring without killing."
  107. (interactive "p")
  108. (kill-ring-save (point)
  109. (save-excursion
  110. (and arg (forward-visible-line (- arg 1)))
  111. (end-of-visible-line)
  112. (point))))
  113. ;;;###autoload
  114. (defun al/backward-save-line (arg)
  115. "Similar to `al/backward-kill-line' but save in a kill ring without killing."
  116. (interactive "p")
  117. (kill-ring-save (point)
  118. (save-excursion
  119. (forward-visible-line (- 1 arg))
  120. (point))))
  121. ;;;###autoload
  122. (defun al/save-whole-line (arg)
  123. "Save current line as if killed, but don't kill it.
  124. With ARG, save that many lines."
  125. (interactive "p")
  126. (save-excursion
  127. (and (< arg 0)
  128. (forward-visible-line 1))
  129. (kill-ring-save (point-at-bol)
  130. (progn
  131. (forward-visible-line arg)
  132. (point)))))
  133. ;; Some ideas came from
  134. ;; <http://stackoverflow.com/questions/88399/how-do-i-duplicate-a-whole-line-in-emacs/>.
  135. ;;;###autoload
  136. (defun al/duplicate-dwim (&optional n)
  137. "Duplicate current line, or region if it is active.
  138. Leave the point on the last copy.
  139. With argument N, make N copies.
  140. With negative N, comment everything except the last copy."
  141. (interactive "*p")
  142. (or n (setq n 1))
  143. (let ((regionp (region-active-p)))
  144. (cl-multiple-value-bind (beg end col)
  145. (if regionp
  146. (list (region-beginning)
  147. (region-end)
  148. nil)
  149. (list (line-beginning-position)
  150. (line-beginning-position 2)
  151. (current-column)))
  152. (let ((text (buffer-substring-no-properties beg end)))
  153. (goto-char beg)
  154. (dotimes (i (abs n))
  155. (let ((beg (point)))
  156. (insert text)
  157. (when (< n 0)
  158. (comment-region beg (point))))))
  159. (or regionp (move-to-column col)))))
  160. ;;;###autoload
  161. (defun al/save-word (arg)
  162. "Save characters forward until encountering the end of a word.
  163. Save word as if killed, but don't kill it.
  164. With argument ARG, do this that many times."
  165. (interactive "p")
  166. (kill-ring-save (point)
  167. (save-excursion (forward-word arg) (point))))
  168. ;;;###autoload
  169. (defun al/backward-save-word (arg)
  170. "Save characters backward until encountering the end of a word.
  171. Save word as if killed, but don't kill it.
  172. With argument ARG, do this that many times."
  173. (interactive "p")
  174. (al/save-word (- (or arg 1))))
  175. ;;;###autoload
  176. (defun al/save-sexp (arg)
  177. "Save characters forward until encountering the end of a sexp.
  178. Save sexp as if killed, but don't kill it.
  179. With argument ARG, do this that many times."
  180. (interactive "p")
  181. (kill-ring-save (point)
  182. (save-excursion (forward-sexp arg) (point))))
  183. ;;;###autoload
  184. (defun al/backward-save-sexp (arg)
  185. "Save characters backward until encountering the end of a sexp.
  186. Save sexp as if killed, but don't kill it.
  187. With argument ARG, do this that many times."
  188. (interactive "p")
  189. (al/save-sexp (- (or arg 1))))
  190. (declare-function org-link-unescape "org" (str))
  191. ;;;###autoload
  192. (defun al/decode-region (beg end)
  193. "Replace selected text hexified by a browser with decoded one."
  194. (interactive "r")
  195. (require 'org)
  196. (let ((str (org-link-unescape
  197. (buffer-substring-no-properties beg end))))
  198. (delete-region beg end)
  199. (goto-char beg)
  200. (insert str)
  201. (message "String '%s' was decoded." str)))
  202. (defun al/get-string (&optional msg)
  203. "Return a string from selected region or prompt for it.
  204. Use message MSG in a prompt."
  205. (if (use-region-p)
  206. (buffer-substring-no-properties (region-beginning) (region-end))
  207. (read-string (or msg "Enter a string: "))))
  208. ;;;###autoload
  209. (defun al/downcase-dwim (arg)
  210. "Use `downcase-region', if region is active, and `downcase-word' otherwise."
  211. (interactive "p")
  212. (if (use-region-p)
  213. (downcase-region (region-beginning) (region-end))
  214. (downcase-word arg)))
  215. ;;;###autoload
  216. (defun al/upcase-dwim (arg)
  217. "Use `upcase-region', if region is active, and `upcase-word' otherwise."
  218. (interactive "p")
  219. (if (use-region-p)
  220. (upcase-region (region-beginning) (region-end))
  221. (upcase-word arg)))
  222. ;;;###autoload
  223. (defun al/capitalize-dwim (arg)
  224. "Use `capitalize-region', if region is active, and `capitalize-word' otherwise."
  225. (interactive "p")
  226. (if (use-region-p)
  227. (capitalize-region (region-beginning) (region-end))
  228. (capitalize-word arg)))
  229. ;;;###autoload
  230. (defun al/delete-horizontal-space (&optional direction)
  231. "Delete all spaces and tabs around point.
  232. If DIRECTION is positive, delete them after point,
  233. if it's negative - delete before point."
  234. (interactive "*P")
  235. (setq direction
  236. (cond
  237. ((listp direction) 0)
  238. ((or (and (equal '- direction))
  239. (and (numberp direction) (< direction 0)))
  240. -1)
  241. (t 1)))
  242. (let* ((cur (point))
  243. (beg (if (> direction 0)
  244. cur
  245. (skip-chars-backward " \t")
  246. (constrain-to-field nil cur)))
  247. (end (if (< direction 0)
  248. cur
  249. (skip-chars-forward " \t")
  250. (constrain-to-field nil cur t))))
  251. (delete-region beg end)))
  252. ;;;###autoload
  253. (defun al/comment-dwirm (arg)
  254. "Call the comment command you want (Do What I Really Mean).
  255. Similar to `comment-dwim' except if the region is not active,
  256. call `comment-line'."
  257. (interactive "p")
  258. (if (use-region-p)
  259. (comment-dwim nil)
  260. (al/comment-line arg)))
  261. ;;;###autoload
  262. (defun al/comment-line (arg)
  263. "Comment or uncomment current line.
  264. If a prefix ARG is non-nil, use that many lines."
  265. (interactive "p")
  266. (or (> arg 0)
  267. (error "I don't want to comment previous lines"))
  268. (comment-or-uncomment-region (point-at-bol)
  269. (point-at-eol arg)))
  270. ;;;###autoload
  271. (defun al/dabbrev-expand-word (arg)
  272. "Expand current word.
  273. Like `dabbrev-expand' but use word symbols only."
  274. (interactive "*P")
  275. (let ((dabbrev-abbrev-char-regexp "\\sw"))
  276. (dabbrev-expand arg)))
  277. ;;; Changing the case of previous word(s)
  278. ;; Idea from <http://www.emacswiki.org/emacs/sequential-command.el>.
  279. ;; Example of key bindings:
  280. ;; (global-set-key (kbd "s-d") 'al/downcase-word-backward)
  281. ;; (global-set-key (kbd "s-c") 'al/capitalize-word-backward)
  282. ;; (global-set-key (kbd "s-u") 'al/upcase-word-backward)
  283. ;; When a key binding is pressed, the previous word is changed, if it
  284. ;; (or another key bound to those function) is pressed again, the word
  285. ;; before the previous is changed and so on.
  286. (defvar al/word-position nil
  287. "Last saved position.
  288. Used for `al/downcase-word-backward',
  289. `al/capitalize-word-backward' and `al/upcase-word-backward'.")
  290. (defvar al/word-seq-functions nil
  291. "List of commands for sequential modifying the case of a word.")
  292. (defmacro al/change-word-backward (name fun)
  293. "Make a function for sequential changing previous word(s).
  294. Resulting function `al/NAME-word-backward' will be added to
  295. `al/word-seq-functions'.
  296. Function FUN is called in body of the resulting function for updating
  297. the word. It should accept a number of modified words as argument."
  298. (let ((fun-name (intern (concat "al/" name "-word-backward"))))
  299. `(progn
  300. (cl-pushnew ',fun-name al/word-seq-functions)
  301. (defun ,fun-name (arg)
  302. ,(concat (capitalize name)
  303. " previous word (or ARG words), do not move the point.\n"
  304. "Multiple calls will change previous words sequentially.")
  305. (interactive "p")
  306. (save-excursion
  307. (when (memq last-command al/word-seq-functions)
  308. (goto-char al/word-position))
  309. (backward-word arg)
  310. (setq al/word-position (point))
  311. (,fun arg))))))
  312. (al/change-word-backward "downcase" downcase-word)
  313. (al/change-word-backward "capitalize" capitalize-word)
  314. (al/change-word-backward "upcase" upcase-word)
  315. ;;;###autoload (autoload 'al/downcase-word-backward "al-text-cmd" nil t)
  316. ;;;###autoload (autoload 'al/capitalize-word-backward "al-text-cmd" nil t)
  317. ;;;###autoload (autoload 'al/upcase-word-backward "al-text-cmd" nil t)
  318. ;;; Moving
  319. ;;;###autoload
  320. (defun al/beginning-of-line ()
  321. "Move point to beginning of current line.
  322. If the point is in the beginning of line already,
  323. move to beginning of previous one."
  324. (interactive)
  325. (beginning-of-line (if (= (point) (point-at-bol)) 0 1)))
  326. ;;;###autoload
  327. (defun al/end-of-line ()
  328. "Move point to end of current line.
  329. If the point is in the end of line already,
  330. move to end of next one."
  331. (interactive)
  332. (end-of-line (if (= (point) (point-at-eol)) 2 1)))
  333. ;;;###autoload
  334. (defun al/recenter-top ()
  335. "Move current line to the top (+1) of the window."
  336. (interactive)
  337. (recenter-top-bottom 1))
  338. ;;;###autoload
  339. (defun al/recenter-end-of-buffer-top ()
  340. "Move the last line (-1) of the buffer to the top of the window."
  341. (interactive)
  342. (goto-char (point-max))
  343. (recenter-top-bottom 0)
  344. (forward-line -2))
  345. ;;; Misc
  346. (defvar al/check-parens-modes
  347. '(emacs-lisp-mode lisp-mode scheme-mode)
  348. "List of modes where `al/check-parens' is called.")
  349. ;;;###autoload
  350. (defun al/check-parens ()
  351. "Run `check-parens' if current mode is one of `al/check-parens-modes'."
  352. (when (memq major-mode al/check-parens-modes)
  353. (check-parens)))
  354. (provide 'al-text-cmd)
  355. ;;; al-text-cmd.el ends here