fields.el 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. ;;; srecode/fields.el --- Handling type-in fields in a buffer.
  2. ;;
  3. ;; Copyright (C) 2009-2012 Free Software Foundation, Inc.
  4. ;;
  5. ;; Author: Eric M. Ludlam <eric@siege-engine.com>
  6. ;; This file is part of GNU Emacs.
  7. ;; GNU Emacs is free software: you can redistribute it and/or modify
  8. ;; it under the terms of the GNU General Public License as published by
  9. ;; the Free Software Foundation, either version 3 of the License, or
  10. ;; (at your option) any later version.
  11. ;; GNU Emacs is distributed in the hope that it will be useful,
  12. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;; GNU General Public License for more details.
  15. ;; You should have received a copy of the GNU General Public License
  16. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  17. ;;; Commentary:
  18. ;;
  19. ;; Idea courtesy of yasnippets.
  20. ;;
  21. ;; If someone prefers not to type unknown dictionary entries into
  22. ;; mini-buffer prompts, it could instead use in-buffer fields.
  23. ;;
  24. ;; A template-region specifies an area in which the fields exist. If
  25. ;; the cursor exits the region, all fields are cleared.
  26. ;;
  27. ;; Each field is independent, but some are linked together by name.
  28. ;; Typing in one will cause the matching ones to change in step.
  29. ;;
  30. ;; Each field has 2 overlays. The second overlay allows control in
  31. ;; the character just after the field, but does not highlight it.
  32. ;; @TODO - Cancel an old field array if a new one is about to be created!
  33. ;; Keep this library independent of SRecode proper.
  34. (require 'eieio)
  35. ;;; Code:
  36. (defvar srecode-field-archive nil
  37. "While inserting a set of fields, collect in this variable.
  38. Once an insertion set is done, these fields will be activated.")
  39. ;;; Customization
  40. ;;
  41. (defface srecode-field-face
  42. '((((class color) (background dark))
  43. (:underline "green"))
  44. (((class color) (background light))
  45. (:underline "green4")))
  46. "*Face used to specify editable fields from a template."
  47. :group 'semantic-faces)
  48. (defcustom srecode-fields-exit-confirmation nil
  49. "Ask for confirmation before leaving field editing mode."
  50. :group 'srecode
  51. :type 'boolean)
  52. ;;; BASECLASS
  53. ;;
  54. ;; Fields and the template region share some basic overlay features.
  55. (defclass srecode-overlaid ()
  56. ((overlay :documentation
  57. "Overlay representing this field.
  58. The overlay will crossreference this object.")
  59. )
  60. "An object that gets automatically bound to an overlay.
  61. Has virtual :start and :end initializers.")
  62. (defmethod initialize-instance ((olaid srecode-overlaid) &optional args)
  63. "Initialize OLAID, being sure it archived."
  64. ;; Extract :start and :end from the olaid list.
  65. (let ((newargs nil)
  66. (olay nil)
  67. start end
  68. )
  69. (while args
  70. (cond ((eq (car args) :start)
  71. (setq args (cdr args))
  72. (setq start (car args))
  73. (setq args (cdr args))
  74. )
  75. ((eq (car args) :end)
  76. (setq args (cdr args))
  77. (setq end (car args))
  78. (setq args (cdr args))
  79. )
  80. (t
  81. (push (car args) newargs)
  82. (setq args (cdr args))
  83. (push (car args) newargs)
  84. (setq args (cdr args)))
  85. ))
  86. ;; Create a temporary overlay now. We have to use an overlay and
  87. ;; not a marker because of the in-front insertion rules. The rules
  88. ;; are backward from what is wanted while typing.
  89. (setq olay (make-overlay start end (current-buffer) t nil))
  90. (overlay-put olay 'srecode-init-only t)
  91. (oset olaid overlay olay)
  92. (call-next-method olaid (nreverse newargs))
  93. ))
  94. (defmethod srecode-overlaid-activate ((olaid srecode-overlaid))
  95. "Activate the overlaid area."
  96. (let* ((ola (oref olaid overlay))
  97. (start (overlay-start ola))
  98. (end (overlay-end ola))
  99. ;; Create a new overlay here.
  100. (ol (make-overlay start end (current-buffer) nil t)))
  101. ;; Remove the old one.
  102. (delete-overlay ola)
  103. (overlay-put ol 'srecode olaid)
  104. (oset olaid overlay ol)
  105. ))
  106. (defmethod srecode-delete ((olaid srecode-overlaid))
  107. "Delete the overlay from OLAID."
  108. (delete-overlay (oref olaid overlay))
  109. (slot-makeunbound olaid 'overlay)
  110. )
  111. (defmethod srecode-empty-region-p ((olaid srecode-overlaid))
  112. "Return non-nil if the region covered by OLAID is of length 0."
  113. (= 0 (srecode-region-size olaid)))
  114. (defmethod srecode-region-size ((olaid srecode-overlaid))
  115. "Return the length of region covered by OLAID."
  116. (let ((start (overlay-start (oref olaid overlay)))
  117. (end (overlay-end (oref olaid overlay))))
  118. (- end start)))
  119. (defmethod srecode-point-in-region-p ((olaid srecode-overlaid))
  120. "Return non-nil if point is in the region of OLAID."
  121. (let ((start (overlay-start (oref olaid overlay)))
  122. (end (overlay-end (oref olaid overlay))))
  123. (and (>= (point) start) (<= (point) end))))
  124. (defun srecode-overlaid-at-point (class)
  125. "Return a list of overlaid fields of type CLASS at point."
  126. (let ((ol (overlays-at (point)))
  127. (ret nil))
  128. (while ol
  129. (let ((tmp (overlay-get (car ol) 'srecode)))
  130. (when (and tmp (object-of-class-p tmp class))
  131. (setq ret (cons tmp ret))))
  132. (setq ol (cdr ol)))
  133. (car (nreverse ret))))
  134. (defmethod srecode-overlaid-text ((olaid srecode-overlaid) &optional set-to)
  135. "Return the text under OLAID.
  136. If SET-TO is a string, then replace the text of OLAID wit SET-TO."
  137. (let* ((ol (oref olaid overlay))
  138. (start (overlay-start ol)))
  139. (if (not (stringp set-to))
  140. ;; Just return it.
  141. (buffer-substring-no-properties start (overlay-end ol))
  142. ;; Replace it.
  143. (save-excursion
  144. (delete-region start (overlay-end ol))
  145. (goto-char start)
  146. (insert set-to)
  147. (move-overlay ol start (+ start (length set-to))))
  148. nil)))
  149. ;;; INSERTED REGION
  150. ;;
  151. ;; Managing point-exit, and flushing fields.
  152. (defclass srecode-template-inserted-region (srecode-overlaid)
  153. ((fields :documentation
  154. "A list of field overlays in this region.")
  155. (active-region :allocation :class
  156. :initform nil
  157. :documentation
  158. "The template region currently being handled.")
  159. )
  160. "Manage a buffer region in which fields exist.")
  161. (defmethod initialize-instance ((ir srecode-template-inserted-region)
  162. &rest args)
  163. "Initialize IR, capturing the active fields, and creating the overlay."
  164. ;; Fill in the fields
  165. (oset ir fields srecode-field-archive)
  166. (setq srecode-field-archive nil)
  167. ;; Initialize myself first.
  168. (call-next-method)
  169. )
  170. (defmethod srecode-overlaid-activate ((ir srecode-template-inserted-region))
  171. "Activate the template area for IR."
  172. ;; Activate all our fields
  173. (dolist (F (oref ir fields))
  174. (srecode-overlaid-activate F))
  175. ;; Activate our overlay.
  176. (call-next-method)
  177. ;; Position the cursor at the first field
  178. (let ((first (car (oref ir fields))))
  179. (goto-char (overlay-start (oref first overlay))))
  180. ;; Set ourselves up as 'active'
  181. (oset ir active-region ir)
  182. ;; Setup the post command hook.
  183. (add-hook 'post-command-hook 'srecode-field-post-command t t)
  184. )
  185. (defmethod srecode-delete ((ir srecode-template-inserted-region))
  186. "Call into our base, but also clear out the fields."
  187. ;; Clear us out of the baseclass.
  188. (oset ir active-region nil)
  189. ;; Clear our fields.
  190. (mapc 'srecode-delete (oref ir fields))
  191. ;; Call to our base
  192. (call-next-method)
  193. ;; Clear our hook.
  194. (remove-hook 'post-command-hook 'srecode-field-post-command t)
  195. )
  196. (defsubst srecode-active-template-region ()
  197. "Return the active region for template fields."
  198. (oref srecode-template-inserted-region active-region))
  199. (defun srecode-field-post-command ()
  200. "Srecode field handler in the post command hook."
  201. (let ((ar (srecode-active-template-region))
  202. )
  203. (if (not ar)
  204. ;; Find a bug and fix it.
  205. (remove-hook 'post-command-hook 'srecode-field-post-command t)
  206. (if (srecode-point-in-region-p ar)
  207. nil ;; Keep going
  208. ;; We moved out of the template. Cancel the edits.
  209. (srecode-delete ar)))
  210. ))
  211. ;;; FIELDS
  212. (defclass srecode-field (srecode-overlaid)
  213. ((tail :documentation
  214. "Overlay used on character just after this field.
  215. Used to provide useful keybindings there.")
  216. (name :initarg :name
  217. :documentation
  218. "The name of this field.
  219. Usually initialized from the dictionary entry name that
  220. the users needs to edit.")
  221. (prompt :initarg :prompt
  222. :documentation
  223. "A prompt string to use if this were in the minibuffer.
  224. Display when the cursor enters this field.")
  225. (read-fcn :initarg :read-fcn
  226. :documentation
  227. "A function that would be used to read a string.
  228. Try to use this to provide useful completion when available.")
  229. )
  230. "Representation of one field.")
  231. (defvar srecode-field-keymap
  232. (let ((km (make-sparse-keymap)))
  233. (define-key km "\C-i" 'srecode-field-next)
  234. (define-key km "\M-\C-i" 'srecode-field-prev)
  235. (define-key km "\C-e" 'srecode-field-end)
  236. (define-key km "\C-a" 'srecode-field-start)
  237. (define-key km "\M-m" 'srecode-field-start)
  238. (define-key km "\C-c\C-c" 'srecode-field-exit-ask)
  239. km)
  240. "Keymap applied to field overlays.")
  241. (defmethod initialize-instance ((field srecode-field) &optional args)
  242. "Initialize FIELD, being sure it archived."
  243. (add-to-list 'srecode-field-archive field t)
  244. (call-next-method)
  245. )
  246. (defmethod srecode-overlaid-activate ((field srecode-field))
  247. "Activate the FIELD area."
  248. (call-next-method)
  249. (let* ((ol (oref field overlay))
  250. (end nil)
  251. (tail nil))
  252. (overlay-put ol 'face 'srecode-field-face)
  253. (overlay-put ol 'keymap srecode-field-keymap)
  254. (overlay-put ol 'modification-hooks '(srecode-field-mod-hook))
  255. (overlay-put ol 'insert-behind-hooks '(srecode-field-behind-hook))
  256. (overlay-put ol 'insert-in-front-hooks '(srecode-field-mod-hook))
  257. (setq end (overlay-end ol))
  258. (setq tail (make-overlay end (+ end 1) (current-buffer)))
  259. (overlay-put tail 'srecode field)
  260. (overlay-put tail 'keymap srecode-field-keymap)
  261. (overlay-put tail 'face 'srecode-field-face)
  262. (oset field tail tail)
  263. )
  264. )
  265. (defmethod srecode-delete ((olaid srecode-field))
  266. "Delete our secondary overlay."
  267. ;; Remove our spare overlay
  268. (delete-overlay (oref olaid tail))
  269. (slot-makeunbound olaid 'tail)
  270. ;; Do our baseclass work.
  271. (call-next-method)
  272. )
  273. (defvar srecode-field-replication-max-size 100
  274. "Maximum size of a field before canceling replication.")
  275. (defun srecode-field-mod-hook (ol after start end &optional pre-len)
  276. "Modification hook for the field overlay.
  277. OL is the overlay.
  278. AFTER is non-nil if it is called after the change.
  279. START and END are the bounds of the change.
  280. PRE-LEN is used in the after mode for the length of the changed text."
  281. (when (and after (not undo-in-progress))
  282. (let* ((field (overlay-get ol 'srecode))
  283. (inhibit-point-motion-hooks t)
  284. (inhibit-modification-hooks t)
  285. )
  286. ;; Sometimes a field is deleted, but we might still get a stray
  287. ;; event. Let's just ignore those events.
  288. (when (slot-boundp field 'overlay)
  289. ;; First, fixup the two overlays, in case they got confused.
  290. (let ((main (oref field overlay))
  291. (tail (oref field tail)))
  292. (move-overlay main
  293. (overlay-start main)
  294. (1- (overlay-end tail)))
  295. (move-overlay tail
  296. (1- (overlay-end tail))
  297. (overlay-end tail)))
  298. ;; Now capture text from the main overlay, and propagate it.
  299. (let* ((new-text (srecode-overlaid-text field))
  300. (region (srecode-active-template-region))
  301. (allfields (when region (oref region fields)))
  302. (name (oref field name)))
  303. (dolist (F allfields)
  304. (when (and (not (eq F field))
  305. (string= name (oref F name)))
  306. (if (> (length new-text) srecode-field-replication-max-size)
  307. (message "Field size too large for replication.")
  308. ;; If we find other fields with the same name, then keep
  309. ;; then all together. Disable change hooks to make sure
  310. ;; we don't get a recursive edit.
  311. (srecode-overlaid-text F new-text)
  312. ))))
  313. ))))
  314. (defun srecode-field-behind-hook (ol after start end &optional pre-len)
  315. "Modification hook for the field overlay.
  316. OL is the overlay.
  317. AFTER is non-nil if it is called after the change.
  318. START and END are the bounds of the change.
  319. PRE-LEN is used in the after mode for the length of the changed text."
  320. (when after
  321. (let* ((field (overlay-get ol 'srecode))
  322. )
  323. (move-overlay ol (overlay-start ol) end)
  324. (srecode-field-mod-hook ol after start end pre-len))
  325. ))
  326. (defmethod srecode-field-goto ((field srecode-field))
  327. "Goto the FIELD."
  328. (goto-char (overlay-start (oref field overlay))))
  329. (defun srecode-field-next ()
  330. "Move to the next field."
  331. (interactive)
  332. (let* ((f (srecode-overlaid-at-point 'srecode-field))
  333. (tr (srecode-overlaid-at-point 'srecode-template-inserted-region))
  334. )
  335. (when (not f) (error "Not in a field"))
  336. (when (not tr) (error "Not in a template region"))
  337. (let ((fields (oref tr fields)))
  338. (while fields
  339. ;; Loop over fields till we match. Then move to the next one.
  340. (when (eq f (car fields))
  341. (if (cdr fields)
  342. (srecode-field-goto (car (cdr fields)))
  343. (srecode-field-goto (car (oref tr fields))))
  344. (setq fields nil)
  345. )
  346. (setq fields (cdr fields))))
  347. ))
  348. (defun srecode-field-prev ()
  349. "Move to the prev field."
  350. (interactive)
  351. (let* ((f (srecode-overlaid-at-point 'srecode-field))
  352. (tr (srecode-overlaid-at-point 'srecode-template-inserted-region))
  353. )
  354. (when (not f) (error "Not in a field"))
  355. (when (not tr) (error "Not in a template region"))
  356. (let ((fields (reverse (oref tr fields))))
  357. (while fields
  358. ;; Loop over fields till we match. Then move to the next one.
  359. (when (eq f (car fields))
  360. (if (cdr fields)
  361. (srecode-field-goto (car (cdr fields)))
  362. (srecode-field-goto (car (oref tr fields))))
  363. (setq fields nil)
  364. )
  365. (setq fields (cdr fields))))
  366. ))
  367. (defun srecode-field-end ()
  368. "Move to the end of this field."
  369. (interactive)
  370. (let* ((f (srecode-overlaid-at-point 'srecode-field)))
  371. (goto-char (overlay-end (oref f overlay)))))
  372. (defun srecode-field-start ()
  373. "Move to the end of this field."
  374. (interactive)
  375. (let* ((f (srecode-overlaid-at-point 'srecode-field)))
  376. (goto-char (overlay-start (oref f overlay)))))
  377. (defun srecode-field-exit-ask ()
  378. "Ask if the user wants to exit field-editing mini-mode."
  379. (interactive)
  380. (when (or (not srecode-fields-exit-confirmation)
  381. (y-or-n-p "Exit field-editing mode? "))
  382. (srecode-delete (srecode-active-template-region))))
  383. (provide 'srecode/fields)
  384. ;;; srecode/fields.el ends here