Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

948 lines
39 KiB

  1. ;;; iedit-lib.el --- APIs for editing multiple regions in the same way
  2. ;;; simultaneously.
  3. ;; Copyright (C) 2010, 2011, 2012 Victor Ren
  4. ;; Time-stamp: <2016-06-24 14:02:51 Victor Ren>
  5. ;; Author: Victor Ren <victorhge@gmail.com>
  6. ;; Keywords: occurrence region simultaneous rectangle refactoring
  7. ;; Version: 0.9.9
  8. ;; X-URL: http://www.emacswiki.org/emacs/Iedit
  9. ;; Compatibility: GNU Emacs: 22.x, 23.x, 24.x
  10. ;; This file is not part of GNU Emacs, but it is distributed under
  11. ;; the same terms as GNU Emacs.
  12. ;; GNU Emacs is free software: you can redistribute it and/or modify
  13. ;; it under the terms of the GNU General Public License as published by
  14. ;; the Free Software Foundation, either version 3 of the License, or
  15. ;; (at your option) any later version.
  16. ;; GNU Emacs is distributed in the hope that it will be useful,
  17. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. ;; GNU General Public License for more details.
  20. ;; You should have received a copy of the GNU General Public License
  21. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  22. ;;; Commentary:
  23. ;; This package is iedit APIs library that allow you to write your own minor mode.
  24. ;; The functionalities of the APIs:
  25. ;; - Create occurrence overlays
  26. ;; - Navigate in the occurrence overlays
  27. ;; - Modify the occurrences
  28. ;; - Hide/unhide
  29. ;; - Other basic support APIs
  30. ;;; todo:
  31. ;; - Update comments for APIs
  32. ;; - Add more easy access keys for whole occurrence
  33. ;;; Code:
  34. (eval-when-compile (require 'cl))
  35. (defgroup iedit nil
  36. "Edit multiple regions in the same way simultaneously."
  37. :prefix "iedit-"
  38. :group 'replace
  39. :group 'convenience)
  40. (defface iedit-occurrence
  41. '((t :inherit highlight))
  42. "*Face used for the occurrences' default values."
  43. :group 'iedit)
  44. (defface iedit-read-only-occurrence
  45. '((t :inherit region))
  46. "*Face used for the read-only occurrences' default values."
  47. :group 'iedit)
  48. (defcustom iedit-case-sensitive-default t
  49. "If no-nil, matching is case sensitive."
  50. :type 'boolean
  51. :group 'iedit)
  52. (defcustom iedit-transient-mark-sensitive t
  53. "If no-nil, Iedit mode is sensitive to the Transient Mark mode.
  54. It means Iedit works as expected only when regions are
  55. highlighted. If you want to use iedit without Transient Mark
  56. mode, set it as nil."
  57. :type 'boolean
  58. :group 'iedit)
  59. (defcustom iedit-overlay-priority 200
  60. "The priority of the overlay used to indicate matches."
  61. :type 'integer
  62. :group 'iedit)
  63. (defvar iedit-occurrences-overlays nil
  64. "The occurrences slot contains a list of overlays used to
  65. indicate the position of each editable occurrence. In addition, the
  66. occurrence overlay is used to provide a different face
  67. configurable via `iedit-occurrence'.")
  68. (defvar iedit-read-only-occurrences-overlays nil
  69. "The occurrences slot contains a list of overlays used to
  70. indicate the position of each read-only occurrence. In addition, the
  71. occurrence overlay is used to provide a different face
  72. configurable via `iedit-ready-only-occurrence'.")
  73. (defvar iedit-case-sensitive iedit-case-sensitive-default
  74. "This is buffer local variable.
  75. If no-nil, matching is case sensitive.")
  76. (defvar iedit-unmatched-lines-invisible nil
  77. "This is buffer local variable which indicates whether
  78. unmatched lines are hided.")
  79. (defvar iedit-forward-success t
  80. "This is buffer local variable which indicates the moving
  81. forward or backward successful")
  82. (defvar iedit-before-modification-string ""
  83. "This is buffer local variable which is the buffer substring
  84. that is going to be changed.")
  85. (defvar iedit-before-modification-undo-list nil
  86. "This is buffer local variable which is the buffer undo list before modification.")
  87. ;; `iedit-update-occurrences' gets called twice when change==0 and
  88. ;; occurrence is zero-width (beg==end) -- for front and back insertion.
  89. (defvar iedit-skip-modification-once t
  90. "Variable used to skip first modification hook run when
  91. insertion against a zero-width occurrence.")
  92. (defvar iedit-aborting nil
  93. "This is buffer local variable which indicates Iedit mode is aborting.")
  94. (defvar iedit-aborting-hook nil
  95. "Functions to call before iedit-abort. Normally it should be mode exit function.")
  96. (defvar iedit-post-undo-hook-installed nil
  97. "This is buffer local variable which indicated if
  98. `iedit-post-undo' is installed in `post-command-hook'.")
  99. (defvar iedit-buffering nil
  100. "This is buffer local variable which indicates iedit-mode is
  101. buffering, which means the modification to the current occurrence
  102. is not applied to other occurrences when it is true.")
  103. (defvar iedit-occurrence-context-lines 1
  104. "The number of lines before or after the occurrence.")
  105. (make-variable-buffer-local 'iedit-occurrences-overlays)
  106. (make-variable-buffer-local 'iedit-read-only-occurrences-overlays)
  107. (make-variable-buffer-local 'iedit-unmatched-lines-invisible)
  108. (make-local-variable 'iedit-case-sensitive)
  109. (make-variable-buffer-local 'iedit-forward-success)
  110. (make-variable-buffer-local 'iedit-before-modification-string)
  111. (make-variable-buffer-local 'iedit-before-modification-undo-list)
  112. (make-variable-buffer-local 'iedit-skip-modification-once)
  113. (make-variable-buffer-local 'iedit-aborting)
  114. (make-variable-buffer-local 'iedit-buffering)
  115. (make-variable-buffer-local 'iedit-post-undo-hook-installed)
  116. (make-variable-buffer-local 'iedit-occurrence-context-lines)
  117. (defconst iedit-occurrence-overlay-name 'iedit-occurrence-overlay-name)
  118. (defconst iedit-invisible-overlay-name 'iedit-invisible-overlay-name)
  119. ;;; Define Iedit mode map
  120. (defvar iedit-lib-keymap
  121. (let ((map (make-sparse-keymap)))
  122. ;; Default key bindings
  123. (define-key map (kbd "TAB") 'iedit-next-occurrence)
  124. (define-key map (kbd "<tab>") 'iedit-next-occurrence)
  125. (define-key map (kbd "<S-tab>") 'iedit-prev-occurrence)
  126. (define-key map (kbd "<S-iso-lefttab>") 'iedit-prev-occurrence)
  127. (define-key map (kbd "<backtab>") 'iedit-prev-occurrence)
  128. (define-key map (kbd "C-'") 'iedit-toggle-unmatched-lines-visible)
  129. map)
  130. "Keymap used while Iedit mode is enabled.")
  131. (defvar iedit-occurrence-keymap-default
  132. (let ((map (make-sparse-keymap)))
  133. ;; (set-keymap-parent map iedit-lib-keymap)
  134. (define-key map (kbd "M-U") 'iedit-upcase-occurrences)
  135. (define-key map (kbd "M-L") 'iedit-downcase-occurrences)
  136. (define-key map (kbd "M-R") 'iedit-replace-occurrences)
  137. (define-key map (kbd "M-SPC") 'iedit-blank-occurrences)
  138. (define-key map (kbd "M-D") 'iedit-delete-occurrences)
  139. (define-key map (kbd "M-N") 'iedit-number-occurrences)
  140. (define-key map (kbd "M-B") 'iedit-toggle-buffering)
  141. (define-key map (kbd "M-<") 'iedit-goto-first-occurrence)
  142. (define-key map (kbd "M->") 'iedit-goto-last-occurrence)
  143. (define-key map (kbd "C-?") 'iedit-help-for-occurrences)
  144. (define-key map [remap keyboard-escape-quit] 'iedit-quit)
  145. (define-key map [remap keyboard-quit] 'iedit-quit)
  146. map)
  147. "Default keymap used within occurrence overlays.")
  148. (defvar iedit-occurrence-keymap 'iedit-occurrence-keymap-default
  149. "Keymap used within occurrence overlays.
  150. It should be set before occurrence overlay is created.")
  151. (make-local-variable 'iedit-occurrence-keymap)
  152. (defun iedit-help-for-occurrences ()
  153. "Display `iedit-occurrence-keymap-default'"
  154. (interactive)
  155. (message (concat (substitute-command-keys "\\[iedit-upcase-occurrences]") "/"
  156. (substitute-command-keys "\\[iedit-downcase-occurrences]") ":up/downcase "
  157. (substitute-command-keys "\\[iedit-replace-occurrences]") ":replace "
  158. (substitute-command-keys "\\[iedit-blank-occurrences]") ":blank "
  159. (substitute-command-keys "\\[iedit-delete-occurrences]") ":delete "
  160. (substitute-command-keys "\\[iedit-number-occurrences]") ":number "
  161. (substitute-command-keys "\\[iedit-toggle-buffering]") ":buffering "
  162. (substitute-command-keys "\\[iedit-goto-first-occurrence]") "/"
  163. (substitute-command-keys "\\[iedit-goto-last-occurrence]") ":first/last "
  164. )))
  165. (defun iedit-quit ()
  166. "Quit the current mode."
  167. (interactive)
  168. (run-hooks 'iedit-aborting-hook))
  169. (defun iedit-make-markers-overlays (markers)
  170. "Create occurrence overlays on a list of markers."
  171. (setq iedit-occurrences-overlays
  172. (mapcar #'(lambda (marker)
  173. (iedit-make-occurrence-overlay (car marker) (cdr marker)))
  174. markers)))
  175. (defun iedit-make-occurrences-overlays (occurrence-regexp beg end)
  176. "Create occurrence overlays for `occurrence-regexp' in a region.
  177. Return the number of occurrences."
  178. (setq iedit-aborting nil)
  179. (setq iedit-occurrences-overlays nil)
  180. (setq iedit-read-only-occurrences-overlays nil)
  181. ;; Find and record each occurrence's markers and add the overlay to the occurrences
  182. (let ((counter 0)
  183. (case-fold-search (not iedit-case-sensitive)))
  184. (save-excursion
  185. (save-window-excursion
  186. (goto-char end)
  187. ;; todo: figure out why re-search-forward is slow without "recenter"
  188. (recenter)
  189. (goto-char beg)
  190. (while (re-search-forward occurrence-regexp end t)
  191. (let ((beginning (match-beginning 0))
  192. (ending (match-end 0)))
  193. (if (text-property-not-all beginning ending 'read-only nil)
  194. (push (iedit-make-read-only-occurrence-overlay beginning ending)
  195. iedit-read-only-occurrences-overlays)
  196. (push (iedit-make-occurrence-overlay beginning ending)
  197. iedit-occurrences-overlays))
  198. (setq counter (1+ counter))))))
  199. counter))
  200. (defun iedit-add-next-occurrence-overlay (occurrence-exp &optional point)
  201. "Create next occurrence overlay for `occurrence-exp'."
  202. (iedit-add-occurrence-overlay occurrence-exp point t))
  203. (defun iedit-add-previous-occurrence-overlay (occurrence-exp &optional point)
  204. "Create previous occurrence overlay for `occurrence-exp'."
  205. (iedit-add-occurrence-overlay occurrence-exp point nil))
  206. (defun iedit-add-occurrence-overlay (occurrence-exp point forward &optional bound)
  207. "Create next or previous occurrence overlay for `occurrence-exp'.
  208. Return the start position of the new occurrence if successful."
  209. (or point
  210. (setq point (point)))
  211. (let ((case-fold-search (not iedit-case-sensitive))
  212. (pos nil))
  213. (save-excursion
  214. (goto-char point)
  215. (if (not (if forward
  216. (re-search-forward occurrence-exp bound t)
  217. (re-search-backward occurrence-exp bound t)))
  218. (message "No more matches.")
  219. (setq pos (match-beginning 0))
  220. (if (or (iedit-find-overlay-at-point (match-beginning 0) 'iedit-occurrence-overlay-name)
  221. (iedit-find-overlay-at-point (match-end 0) 'iedit-occurrence-overlay-name))
  222. (error "Conflict region"))
  223. (push (iedit-make-occurrence-overlay (match-beginning 0)
  224. (match-end 0))
  225. iedit-occurrences-overlays)
  226. (message "Add one match for \"%s\"." (iedit-printable occurrence-exp))
  227. (when iedit-unmatched-lines-invisible
  228. (iedit-show-all)
  229. (iedit-hide-unmatched-lines iedit-occurrence-context-lines))
  230. ))
  231. pos))
  232. (defun iedit-add-region-as-occurrence (beg end)
  233. "Add region as an occurrence.
  234. The length of the region must the same as other occurrences if
  235. there are."
  236. (or (= beg end)
  237. (error "No region"))
  238. (if (null iedit-occurrences-overlays)
  239. (push
  240. (iedit-make-occurrence-overlay beg end)
  241. iedit-occurrences-overlays)
  242. (or (= (- end beg) (iedit-occurrence-string-length))
  243. (error "Wrong region"))
  244. (if (or (iedit-find-overlay-at-point beg 'iedit-occurrence-overlay-name)
  245. (iedit-find-overlay-at-point end 'iedit-occurrence-overlay-name))
  246. (error "Conflict region"))
  247. (push (iedit-make-occurrence-overlay beg end)
  248. iedit-occurrences-overlays)
  249. )) ;; todo test this function
  250. (defun iedit-cleanup ()
  251. "Clean up occurrence overlay, invisible overlay and local variables."
  252. (remove-overlays nil nil iedit-occurrence-overlay-name t)
  253. (iedit-show-all)
  254. (setq iedit-occurrences-overlays nil)
  255. (setq iedit-read-only-occurrences-overlays nil)
  256. (setq iedit-aborting nil)
  257. (setq iedit-before-modification-string "")
  258. (setq iedit-before-modification-undo-list nil))
  259. (defun iedit-make-occurrence-overlay (begin end)
  260. "Create an overlay for an occurrence in Iedit mode.
  261. Add the properties for the overlay: a face used to display a
  262. occurrence's default value, and modification hooks to update
  263. occurrences if the user starts typing."
  264. (let ((occurrence (make-overlay begin end (current-buffer) nil t)))
  265. (overlay-put occurrence iedit-occurrence-overlay-name t)
  266. (overlay-put occurrence 'face 'iedit-occurrence)
  267. (overlay-put occurrence 'keymap iedit-occurrence-keymap)
  268. (overlay-put occurrence 'insert-in-front-hooks '(iedit-update-occurrences))
  269. (overlay-put occurrence 'insert-behind-hooks '(iedit-update-occurrences))
  270. (overlay-put occurrence 'modification-hooks '(iedit-update-occurrences))
  271. (overlay-put occurrence 'priority iedit-overlay-priority)
  272. occurrence))
  273. (defun iedit-make-read-only-occurrence-overlay (begin end)
  274. "Create an overlay for an read-only occurrence in Iedit mode."
  275. (let ((occurrence (make-overlay begin end (current-buffer) nil t)))
  276. (overlay-put occurrence iedit-occurrence-overlay-name t)
  277. (overlay-put occurrence 'face 'iedit-read-only-occurrence)
  278. occurrence))
  279. (defun iedit-make-unmatched-lines-overlay (begin end)
  280. "Create an overlay for lines between two occurrences in Iedit mode."
  281. (let ((unmatched-lines-overlay (make-overlay begin end (current-buffer) nil t)))
  282. (overlay-put unmatched-lines-overlay iedit-invisible-overlay-name t)
  283. (overlay-put unmatched-lines-overlay 'invisible 'iedit-invisible-overlay-name)
  284. ;; (overlay-put unmatched-lines-overlay 'intangible t)
  285. unmatched-lines-overlay))
  286. (defun iedit-post-undo ()
  287. "Check if it is time to abort iedit after undo command is executed.
  288. This is added to `post-command-hook' when undo command is executed
  289. in occurrences."
  290. (if (iedit-same-length)
  291. nil
  292. (run-hooks 'iedit-aborting-hook))
  293. (remove-hook 'post-command-hook 'iedit-post-undo t)
  294. (setq iedit-post-undo-hook-installed nil))
  295. (defun iedit-reset-aborting ()
  296. "Turning Iedit mode off and reset `iedit-aborting'.
  297. This is added to `post-command-hook' when aborting Iedit mode is
  298. decided. `iedit-aborting-hook' is postponed after the current
  299. command is executed for avoiding `iedit-update-occurrences'
  300. is called for a removed overlay."
  301. (run-hooks 'iedit-aborting-hook)
  302. (remove-hook 'post-command-hook 'iedit-reset-aborting t)
  303. (setq iedit-aborting nil))
  304. ;; There are two ways to update all occurrences. One is to redefine all key
  305. ;; stroke map for overlay, the other is to figure out three basic modification
  306. ;; in the modification hook. This function chooses the latter.
  307. (defun iedit-update-occurrences (occurrence after beg end &optional change)
  308. "Update all occurrences.
  309. This modification hook is triggered when a user edits any
  310. occurrence and is responsible for updating all other
  311. occurrences. Refer to `modification-hooks' for more details.
  312. Current supported edits are insertion, yank, deletion and
  313. replacement. If this modification is going out of the
  314. occurrence, it will abort Iedit mode."
  315. (if undo-in-progress
  316. ;; If the "undo" change make occurrences different, it is going to mess up
  317. ;; occurrences. So a check will be done after undo command is executed.
  318. (when (not iedit-post-undo-hook-installed)
  319. (add-hook 'post-command-hook 'iedit-post-undo nil t)
  320. (setq iedit-post-undo-hook-installed t))
  321. (when (not iedit-aborting)
  322. ;; before modification
  323. (if (null after)
  324. (if (or (< beg (overlay-start occurrence))
  325. (> end (overlay-end occurrence)))
  326. (progn (setq iedit-aborting t) ; abort iedit-mode
  327. (add-hook 'post-command-hook 'iedit-reset-aborting nil t))
  328. (setq iedit-before-modification-string
  329. (buffer-substring-no-properties beg end))
  330. ;; Check if this is called twice before modification. When inserting
  331. ;; into zero-width occurrence or between two conjoined occurrences,
  332. ;; both insert-in-front-hooks and insert-behind-hooks will be
  333. ;; called. Two calls will make `iedit-skip-modification-once' true.
  334. (setq iedit-skip-modification-once (not iedit-skip-modification-once)))
  335. ;; after modification
  336. (when (not iedit-buffering)
  337. (if iedit-skip-modification-once
  338. ;; Skip the first hook
  339. (setq iedit-skip-modification-once nil)
  340. (setq iedit-skip-modification-once t)
  341. (when (or (eq 0 change) ;; insertion
  342. (eq beg end) ;; deletion
  343. (not (string= iedit-before-modification-string
  344. (buffer-substring-no-properties beg end))))
  345. (iedit-update-occurrences-2 occurrence after beg end change))))))))
  346. (defun iedit-update-occurrences-2 (occurrence after beg end &optional change)
  347. ""
  348. (let ((inhibit-modification-hooks t)
  349. (offset (- beg (overlay-start occurrence)))
  350. (value (buffer-substring-no-properties beg end)))
  351. (save-excursion
  352. ;; insertion or yank
  353. (if (= 0 change)
  354. (dolist (another-occurrence iedit-occurrences-overlays)
  355. (let* ((beginning (+ (overlay-start another-occurrence) offset))
  356. (ending (+ beginning (- end beg))))
  357. (when (not (eq another-occurrence occurrence))
  358. (goto-char beginning)
  359. (insert-and-inherit value)
  360. ;; todo: reconsider this change Quick fix for
  361. ;; multi-occur occur-edit-mode: multi-occur depend on
  362. ;; after-change-functions to update original
  363. ;; buffer. Since inhibit-modification-hooks is set to
  364. ;; non-nil, after-change-functions hooks are not going
  365. ;; to be called for the changes of other occurrences.
  366. ;; So run the hook here.
  367. (run-hook-with-args 'after-change-functions
  368. beginning
  369. ending
  370. change))
  371. (iedit-move-conjoined-overlays another-occurrence)))
  372. ;; deletion
  373. (dolist (another-occurrence (remove occurrence iedit-occurrences-overlays))
  374. (let ((beginning (+ (overlay-start another-occurrence) offset)))
  375. (delete-region beginning (+ beginning change))
  376. (unless (eq beg end) ;; replacement
  377. (goto-char beginning)
  378. (insert-and-inherit value))
  379. (run-hook-with-args 'after-change-functions
  380. beginning
  381. (+ beginning (- beg end))
  382. change)))))))
  383. (defun iedit-next-occurrence ()
  384. "Move forward to the next occurrence in the `iedit'.
  385. If the point is already in the last occurrences, you are asked to type
  386. another `iedit-next-occurrence', it starts again from the
  387. beginning of the buffer."
  388. (interactive)
  389. (let ((pos (point))
  390. (in-occurrence (get-char-property (point) 'iedit-occurrence-overlay-name)))
  391. (when in-occurrence
  392. (setq pos (next-single-char-property-change pos 'iedit-occurrence-overlay-name)))
  393. (setq pos (next-single-char-property-change pos 'iedit-occurrence-overlay-name))
  394. (if (/= pos (point-max))
  395. (setq iedit-forward-success t)
  396. (if (and iedit-forward-success in-occurrence)
  397. (progn (message "This is the last occurrence.")
  398. (setq iedit-forward-success nil))
  399. (progn
  400. (if (get-char-property (point-min) 'iedit-occurrence-overlay-name)
  401. (setq pos (point-min))
  402. (setq pos (next-single-char-property-change
  403. (point-min)
  404. 'iedit-occurrence-overlay-name)))
  405. (setq iedit-forward-success t)
  406. (message "Located the first occurrence."))))
  407. (when iedit-forward-success
  408. (goto-char pos))))
  409. (defun iedit-prev-occurrence ()
  410. "Move backward to the previous occurrence in the `iedit'.
  411. If the point is already in the first occurrences, you are asked to type
  412. another `iedit-prev-occurrence', it starts again from the end of
  413. the buffer."
  414. (interactive)
  415. (let ((pos (point))
  416. (in-occurrence (get-char-property (point) 'iedit-occurrence-overlay-name)))
  417. (when in-occurrence
  418. (setq pos (previous-single-char-property-change pos 'iedit-occurrence-overlay-name)))
  419. (setq pos (previous-single-char-property-change pos 'iedit-occurrence-overlay-name))
  420. ;; At the start of the first occurrence
  421. (if (or (and (eq pos (point-min))
  422. (not (get-char-property (point-min) 'iedit-occurrence-overlay-name)))
  423. (and (eq (point) (point-min))
  424. in-occurrence))
  425. (if (and iedit-forward-success in-occurrence)
  426. (progn (message "This is the first occurrence.")
  427. (setq iedit-forward-success nil))
  428. (progn
  429. (setq pos (previous-single-char-property-change (point-max) 'iedit-occurrence-overlay-name))
  430. (if (not (get-char-property (- (point-max) 1) 'iedit-occurrence-overlay-name))
  431. (setq pos (previous-single-char-property-change pos 'iedit-occurrence-overlay-name)))
  432. (setq iedit-forward-success t)
  433. (message "Located the last occurrence.")))
  434. (setq iedit-forward-success t))
  435. (when iedit-forward-success
  436. (goto-char pos))))
  437. (defun iedit-goto-first-occurrence ()
  438. "Move to the first occurrence."
  439. (interactive)
  440. (goto-char (iedit-first-occurrence))
  441. (setq iedit-forward-success t)
  442. (message "Located the first occurrence."))
  443. (defun iedit-first-occurrence ()
  444. "return the position of the first occurrence."
  445. (if (get-char-property (point-min) 'iedit-occurrence-overlay-name)
  446. (point-min)
  447. (next-single-char-property-change
  448. (point-min) 'iedit-occurrence-overlay-name)))
  449. (defun iedit-goto-last-occurrence ()
  450. "Move to the last occurrence."
  451. (interactive)
  452. (goto-char (iedit-last-occurrence))
  453. (setq iedit-forward-success t)
  454. (message "Located the last occurrence."))
  455. (defun iedit-last-occurrence ()
  456. "return the position of the last occurrence."
  457. (let ((pos (previous-single-char-property-change (point-max) 'iedit-occurrence-overlay-name)))
  458. (if (not (get-char-property (- (point-max) 1) 'iedit-occurrence-overlay-name))
  459. (setq pos (previous-single-char-property-change pos 'iedit-occurrence-overlay-name)))
  460. pos))
  461. (defun iedit-toggle-unmatched-lines-visible (&optional arg)
  462. "Toggle whether to display unmatched lines.
  463. A prefix ARG specifies how many lines before and after the
  464. occurrences are not hided; negative is treated the same as zero.
  465. If no prefix argument, the prefix argument last time or default
  466. value of `iedit-occurrence-context-lines' is used for this time."
  467. (interactive "P")
  468. (if (null arg)
  469. ;; toggle visible
  470. (progn (setq iedit-unmatched-lines-invisible (not iedit-unmatched-lines-invisible))
  471. (if iedit-unmatched-lines-invisible
  472. (iedit-hide-unmatched-lines iedit-occurrence-context-lines)
  473. (iedit-show-all)))
  474. ;; reset invisible lines
  475. (setq arg (prefix-numeric-value arg))
  476. (if (< arg 0)
  477. (setq arg 0))
  478. (unless (and iedit-unmatched-lines-invisible
  479. (= arg iedit-occurrence-context-lines))
  480. (when iedit-unmatched-lines-invisible
  481. (remove-overlays nil nil iedit-invisible-overlay-name t))
  482. (setq iedit-occurrence-context-lines arg)
  483. (setq iedit-unmatched-lines-invisible t)
  484. (iedit-hide-unmatched-lines iedit-occurrence-context-lines))))
  485. (defun iedit-show-all()
  486. "Show hided lines."
  487. (setq line-move-ignore-invisible nil)
  488. (remove-from-invisibility-spec '(iedit-invisible-overlay-name . t))
  489. (remove-overlays nil nil iedit-invisible-overlay-name t))
  490. (defun iedit-hide-unmatched-lines (context-lines)
  491. "Hide unmatched lines using invisible overlay."
  492. (let ((prev-occurrence-end 1)
  493. (unmatched-lines nil))
  494. (save-excursion
  495. (goto-char (iedit-first-occurrence))
  496. (while (/= (point) (point-max))
  497. ;; Now at the beginning of an occurrence
  498. (let ((current-start (point)))
  499. (forward-line (- context-lines))
  500. (let ((line-beginning (line-beginning-position)))
  501. (if (> line-beginning prev-occurrence-end)
  502. (push (list prev-occurrence-end (1- line-beginning)) unmatched-lines)))
  503. ;; goto the end of the occurrence
  504. (goto-char (next-single-char-property-change current-start 'iedit-occurrence-overlay-name)))
  505. (let ((current-end (point)))
  506. (forward-line context-lines)
  507. (setq prev-occurrence-end (1+ (line-end-position)))
  508. ;; goto the beginning of next occurrence
  509. (goto-char (next-single-char-property-change current-end 'iedit-occurrence-overlay-name))))
  510. (if (< prev-occurrence-end (point-max))
  511. (push (list prev-occurrence-end (point-max)) unmatched-lines))
  512. (when unmatched-lines
  513. (set (make-local-variable 'line-move-ignore-invisible) t)
  514. (add-to-invisibility-spec '(iedit-invisible-overlay-name . t))
  515. (dolist (unmatch unmatched-lines)
  516. (iedit-make-unmatched-lines-overlay (car unmatch) (cadr unmatch)))))
  517. unmatched-lines))
  518. ;;;; functions for overlay keymap
  519. (defun iedit-apply-on-occurrences (function &rest args)
  520. "Call function for each occurrence."
  521. (let ((inhibit-modification-hooks t))
  522. (save-excursion
  523. (dolist (occurrence iedit-occurrences-overlays)
  524. (apply function (overlay-start occurrence) (overlay-end occurrence) args)))))
  525. (defun iedit-upcase-occurrences ()
  526. "Covert occurrences to upper case."
  527. (interactive "*")
  528. (iedit-barf-if-buffering)
  529. (iedit-apply-on-occurrences 'upcase-region))
  530. (when (require 'multiple-cursors-core nil t)
  531. (defun iedit-switch-to-mc-mode ()
  532. "Switch to multiple-cursors-mode. So that you can navigate
  533. out of the occurrence and edit simutaneously with multiple
  534. cursors."
  535. (interactive "*")
  536. (iedit-barf-if-buffering)
  537. (let* ((ov (iedit-find-current-occurrence-overlay))
  538. (offset (- (point) (overlay-start ov)))
  539. (master (point)))
  540. (mc/save-excursion
  541. (dolist (occurrence iedit-occurrences-overlays)
  542. (goto-char (+ (overlay-start occurrence) offset))
  543. (unless (= master (point))
  544. (mc/create-fake-cursor-at-point))
  545. ))
  546. (multiple-cursors-mode 1)
  547. (run-hooks 'iedit-aborting-hook)))
  548. (define-key iedit-occurrence-keymap-default (kbd "M-M") 'iedit-switch-to-mc-mode))
  549. (defun iedit-downcase-occurrences()
  550. "Covert occurrences to lower case."
  551. (interactive "*")
  552. (iedit-barf-if-buffering)
  553. (iedit-apply-on-occurrences 'downcase-region))
  554. (defun iedit-replace-occurrences(&optional to-string)
  555. "Replace occurrences with STRING.
  556. This function preserves case."
  557. (interactive "*")
  558. (iedit-barf-if-buffering)
  559. (let* ((ov (iedit-find-current-occurrence-overlay))
  560. (offset (- (point) (overlay-start ov)))
  561. (from-string (buffer-substring-no-properties
  562. (overlay-start ov)
  563. (overlay-end ov)))
  564. (from-string-lowercase (downcase from-string))
  565. (to-string (if (not to-string)
  566. (read-string "Replace with: "
  567. nil nil
  568. from-string
  569. nil)
  570. to-string)))
  571. (iedit-apply-on-occurrences
  572. (lambda (beg end from-string-lowercase to-string)
  573. (goto-char beg)
  574. (search-forward from-string-lowercase end)
  575. (replace-match to-string nil))
  576. from-string-lowercase to-string)
  577. (goto-char (+ (overlay-start ov) offset))))
  578. (defun iedit-blank-occurrences()
  579. "Replace occurrences with blank spaces."
  580. (interactive "*")
  581. (iedit-barf-if-buffering)
  582. (let* ((ov (car iedit-occurrences-overlays))
  583. (offset (- (point) (overlay-start ov)))
  584. (count (- (overlay-end ov) (overlay-start ov))))
  585. (iedit-apply-on-occurrences
  586. (lambda (beg end )
  587. (delete-region beg end)
  588. (goto-char beg)
  589. (insert-and-inherit (make-string count 32))))
  590. (goto-char (+ (overlay-start ov) offset))))
  591. (defun iedit-delete-occurrences()
  592. "Delete occurrences."
  593. (interactive "*")
  594. (iedit-barf-if-buffering)
  595. (iedit-apply-on-occurrences 'delete-region))
  596. ;; todo: add cancel buffering function
  597. (defun iedit-toggle-buffering ()
  598. "Toggle buffering.
  599. This is intended to improve iedit's response time. If the number
  600. of occurrences are huge, it might be slow to update all the
  601. occurrences for each key stoke. When buffering is on,
  602. modification is only applied to the current occurrence and will
  603. be applied to other occurrences when buffering is off."
  604. (interactive "*")
  605. (if iedit-buffering
  606. (iedit-stop-buffering)
  607. (iedit-start-buffering))
  608. (message (concat "Modification Buffering "
  609. (if iedit-buffering
  610. "started."
  611. "stopped."))))
  612. (defun iedit-start-buffering ()
  613. "Start buffering."
  614. (setq iedit-buffering t)
  615. (setq iedit-before-modification-string (iedit-current-occurrence-string))
  616. (setq iedit-before-modification-undo-list buffer-undo-list)
  617. (message "Start buffering editing..."))
  618. (defun iedit-stop-buffering ()
  619. "Stop buffering and apply the modification to other occurrences.
  620. If current point is not at any occurrence, the buffered
  621. modification is not going to be applied to other occurrences."
  622. (let ((ov (iedit-find-current-occurrence-overlay)))
  623. (when ov
  624. (let* ((beg (overlay-start ov))
  625. (end (overlay-end ov))
  626. (modified-string (buffer-substring-no-properties beg end))
  627. (offset (- (point) beg)) ;; delete-region moves cursor
  628. (inhibit-modification-hooks t))
  629. (when (not (string= iedit-before-modification-string modified-string))
  630. (save-excursion
  631. ;; Rollback the current modification and buffer-undo-list. This is
  632. ;; to avoid the inconsistency if user undoes modifications
  633. (delete-region beg end)
  634. (goto-char beg)
  635. (insert-and-inherit iedit-before-modification-string)
  636. (setq buffer-undo-list iedit-before-modification-undo-list)
  637. (dolist (occurrence iedit-occurrences-overlays) ; todo:extract as a function
  638. (let ((beginning (overlay-start occurrence))
  639. (ending (overlay-end occurrence)))
  640. (delete-region beginning ending)
  641. (unless (eq beg end) ;; replacement
  642. (goto-char beginning)
  643. (insert-and-inherit modified-string))
  644. (iedit-move-conjoined-overlays occurrence))))
  645. (goto-char (+ (overlay-start ov) offset))))))
  646. (setq iedit-buffering nil)
  647. (message "Buffered modification applied.")
  648. (setq iedit-before-modification-undo-list nil))
  649. (defun iedit-move-conjoined-overlays (occurrence)
  650. "This function keeps overlays conjoined after modification.
  651. After modification, conjoined overlays may be overlapped."
  652. (let ((beginning (overlay-start occurrence))
  653. (ending (overlay-end occurrence)))
  654. (unless (= beginning (point-min))
  655. (let ((previous-overlay (iedit-find-overlay-at-point
  656. (1- beginning)
  657. 'iedit-occurrence-overlay-name)))
  658. (if previous-overlay ; two conjoined occurrences
  659. (move-overlay previous-overlay
  660. (overlay-start previous-overlay)
  661. beginning))))
  662. (unless (= ending (point-max))
  663. (let ((next-overlay (iedit-find-overlay-at-point
  664. ending
  665. 'iedit-occurrence-overlay-name)))
  666. (if next-overlay ; two conjoined occurrences
  667. (move-overlay next-overlay ending (overlay-end next-overlay)))))))
  668. (defvar iedit-number-line-counter 1
  669. "Occurrence number for 'iedit-number-occurrences.")
  670. (defun iedit-default-occurrence-number-format (start-at)
  671. (concat "%"
  672. (int-to-string
  673. (length (int-to-string
  674. (1- (+ (length iedit-occurrences-overlays) start-at)))))
  675. "d "))
  676. (defun iedit-number-occurrences (start-at &optional format-string)
  677. "Insert numbers in front of the occurrences.
  678. START-AT, if non-nil, should be a number from which to begin
  679. counting. FORMAT, if non-nil, should be a format string to pass
  680. to `format-string' along with the line count. When called
  681. interactively with a prefix argument, prompt for START-AT and
  682. FORMAT."
  683. (interactive
  684. (if current-prefix-arg
  685. (let* ((start-at (read-number "Number to count from: " 1)))
  686. (list start-at
  687. (read-string "Format string: "
  688. (iedit-default-occurrence-number-format
  689. start-at))))
  690. (list 1 nil)))
  691. (iedit-barf-if-buffering)
  692. (unless format-string
  693. (setq format-string (iedit-default-occurrence-number-format start-at)))
  694. (let ((iedit-number-occurrence-counter start-at)
  695. (inhibit-modification-hooks t))
  696. (save-excursion
  697. (goto-char (iedit-first-occurrence))
  698. (while (/= (point) (point-max))
  699. (insert (format format-string iedit-number-occurrence-counter))
  700. (iedit-move-conjoined-overlays (iedit-find-current-occurrence-overlay))
  701. (setq iedit-number-occurrence-counter
  702. (1+ iedit-number-occurrence-counter))
  703. (goto-char (next-single-char-property-change (point) 'iedit-occurrence-overlay-name))
  704. (goto-char (next-single-char-property-change (point) 'iedit-occurrence-overlay-name))))))
  705. ;;; help functions
  706. (defun iedit-find-current-occurrence-overlay ()
  707. "Return the current occurrence overlay at point or point - 1.
  708. This function is supposed to be called in overlay keymap."
  709. (or (iedit-find-overlay-at-point (point) 'iedit-occurrence-overlay-name)
  710. (iedit-find-overlay-at-point (1- (point)) 'iedit-occurrence-overlay-name)))
  711. (defun iedit-find-overlay-at-point (point property)
  712. "Return the overlay with PROPERTY at POINT."
  713. (let ((overlays (overlays-at point))
  714. found)
  715. (while (and overlays (not found))
  716. (let ((overlay (car overlays)))
  717. (if (overlay-get overlay property)
  718. (setq found overlay)
  719. (setq overlays (cdr overlays)))))
  720. found))
  721. (defun iedit-same-column ()
  722. "Return t if all occurrences are at the same column."
  723. (save-excursion
  724. (let ((column (progn (goto-char (overlay-start (car iedit-occurrences-overlays)))
  725. (current-column)))
  726. (overlays (cdr iedit-occurrences-overlays))
  727. (same t))
  728. (while (and overlays same)
  729. (let ((overlay (car overlays)))
  730. (if (/= (progn (goto-char (overlay-start overlay))
  731. (current-column))
  732. column)
  733. (setq same nil)
  734. (setq overlays (cdr overlays)))))
  735. same)))
  736. (defun iedit-same-length ()
  737. "Return t if all occurrences are the same length."
  738. (save-excursion
  739. (let ((length (iedit-occurrence-string-length))
  740. (overlays (cdr iedit-occurrences-overlays))
  741. (same t))
  742. (while (and overlays same)
  743. (let ((ov (car overlays)))
  744. (if (/= (- (overlay-end ov) (overlay-start ov))
  745. length)
  746. (setq same nil)
  747. (setq overlays (cdr overlays)))))
  748. same)))
  749. ;; This function might be called out of any occurrence
  750. (defun iedit-current-occurrence-string ()
  751. "Return current occurrence string.
  752. Return nil if occurrence string is empty string."
  753. (let ((ov (or (iedit-find-current-occurrence-overlay)
  754. (car iedit-occurrences-overlays))))
  755. (if ov
  756. (let ((beg (overlay-start ov))
  757. (end (overlay-end ov)))
  758. (if (/= beg end)
  759. (buffer-substring-no-properties beg end)
  760. nil))
  761. nil)))
  762. (defun iedit-occurrence-string-length ()
  763. "Return the length of current occurrence string."
  764. (let ((ov (car iedit-occurrences-overlays)))
  765. (- (overlay-end ov) (overlay-start ov))))
  766. (defun iedit-find-overlay (beg end property &optional exclusive)
  767. "Return a overlay with property in region, or out of the region if EXCLUSIVE is not nil."
  768. (if exclusive
  769. (or (iedit-find-overlay-in-region (point-min) beg property)
  770. (iedit-find-overlay-in-region end (point-max) property))
  771. (iedit-find-overlay-in-region beg end property)))
  772. (defun iedit-find-overlay-in-region (beg end property)
  773. "Return a overlay with property in region."
  774. (let ((overlays (overlays-in beg end))
  775. found)
  776. (while (and overlays (not found))
  777. (let ((overlay (car overlays)))
  778. (if (and (overlay-get overlay property)
  779. (>= (overlay-start overlay) beg)
  780. (<= (overlay-end overlay) end))
  781. (setq found overlay)
  782. (setq overlays (cdr overlays)))))
  783. found))
  784. (defun iedit-cleanup-occurrences-overlays (beg end &optional inclusive)
  785. "Remove deleted overlays from list `iedit-occurrences-overlays'."
  786. (if inclusive
  787. (remove-overlays beg end iedit-occurrence-overlay-name t)
  788. (remove-overlays (point-min) beg iedit-occurrence-overlay-name t)
  789. (remove-overlays end (point-max) iedit-occurrence-overlay-name t))
  790. (let (overlays)
  791. (dolist (overlay iedit-occurrences-overlays)
  792. (if (overlay-buffer overlay)
  793. (push overlay overlays)))
  794. (setq iedit-occurrences-overlays overlays)))
  795. (defun iedit-printable (string)
  796. "Return a omitted substring that is not longer than 50.
  797. STRING is already `regexp-quote'ed"
  798. (let ((first-newline-index (string-match "$" string))
  799. (length (length string)))
  800. (if (and first-newline-index
  801. (/= first-newline-index length))
  802. (if (< first-newline-index 50)
  803. (concat (substring string 0 first-newline-index) "...")
  804. (concat (substring string 0 50) "..."))
  805. (if (> length 50)
  806. (concat (substring string 0 50) "...")
  807. string))))
  808. (defun iedit-char-at-bol (&optional N)
  809. "Get char position of the beginning of the current line. If `N'
  810. is given, move forward (or backward) that many lines (using
  811. `forward-line') and get the char position at the beginning of
  812. that line."
  813. (save-excursion
  814. (forward-line (if N N 0))
  815. (point)))
  816. (defun iedit-char-at-eol (&optional N)
  817. "Get char position of the end of the current line. If `N' is
  818. given, move forward (or backward) that many lines (using
  819. `forward-line') and get the char position at the end of that
  820. line."
  821. (save-excursion
  822. (forward-line (if N N 0))
  823. (end-of-line)
  824. (point)))
  825. (defun iedit-region-active ()
  826. "Return t if region is active and not empty.
  827. If variable `iedit-transient-mark-sensitive' is t, active region
  828. means `transient-mark-mode' is on and mark is active. Otherwise,
  829. it just means mark is active."
  830. (and (if iedit-transient-mark-sensitive
  831. transient-mark-mode
  832. t)
  833. mark-active
  834. (not (equal (mark) (point)))))
  835. (defun iedit-barf-if-lib-active()
  836. "Signal error if Iedit lib is active."
  837. (or (and (null iedit-occurrences-overlays)
  838. (null iedit-read-only-occurrences-overlays))
  839. (error "Iedit lib is active")))
  840. (defun iedit-barf-if-buffering()
  841. "Signal error if Iedit lib is buffering."
  842. (or (null iedit-buffering)
  843. (error "Iedit is buffering")))
  844. (provide 'iedit-lib)
  845. ;;; iedit-lib.el ends here
  846. ;; LocalWords: iedit el MERCHANTABILITY kbd isearch todo ert Lindberg Tassilo
  847. ;; LocalWords: eval rect defgroup defcustom boolean defvar assq alist nconc
  848. ;; LocalWords: substring cadr keymap defconst purecopy bkm defun princ prev
  849. ;; LocalWords: iso lefttab backtab upcase downcase concat setq autoload arg
  850. ;; LocalWords: refactoring propertize cond goto nreverse progn rotatef eq elp
  851. ;; LocalWords: dolist pos unmatch args ov sReplace iedit's cdr quote'ed