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.

684 lines
29 KiB

  1. ;;; polymode-methods.el --- Methods for polymode classes -*- lexical-binding: t -*-
  2. ;;
  3. ;; Copyright (C) 2013-2019, Vitalie Spinu
  4. ;; Author: Vitalie Spinu
  5. ;; URL: https://github.com/polymode/polymode
  6. ;;
  7. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  8. ;;
  9. ;; This file is *NOT* part of GNU Emacs.
  10. ;;
  11. ;; This program is free software; you can redistribute it and/or
  12. ;; modify it under the terms of the GNU General Public License as
  13. ;; published by the Free Software Foundation; either version 3, or
  14. ;; (at your option) any later version.
  15. ;;
  16. ;; This program 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 GNU
  19. ;; General Public License for more details.
  20. ;;
  21. ;; You should have received a copy of the GNU General Public License
  22. ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
  23. ;;
  24. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  25. ;;
  26. ;;; Commentary:
  27. ;;
  28. ;;; Code:
  29. (require 'polymode-core)
  30. ;;; INITIALIZATION
  31. (cl-defgeneric pm-initialize (object)
  32. "Initialize current buffer with OBJECT.")
  33. (cl-defmethod pm-initialize ((config pm-polymode))
  34. "Initialization of host buffers.
  35. Ran by the polymode mode function."
  36. ;; Not calling config's '-minor-mode in hosts because this pm-initialize is
  37. ;; called from minor-mode itself in base buffers.
  38. (let* ((hostmode-name (eieio-oref config 'hostmode))
  39. (hostmode (if hostmode-name
  40. (clone (symbol-value hostmode-name))
  41. (pm-host-chunkmode :name "ANY" :mode nil))))
  42. (let ((pm-initialization-in-progress t)
  43. ;; Set if nil! This allows unspecified host chunkmodes to be used in
  44. ;; minor modes.
  45. (host-mode (or (eieio-oref hostmode 'mode)
  46. (oset hostmode :mode major-mode))))
  47. ;; host-mode hooks are run here, but polymode is not initialized
  48. (pm--mode-setup host-mode)
  49. (oset hostmode -buffer (current-buffer))
  50. (oset config -hostmode hostmode)
  51. (setq pm--core-buffer-name (buffer-name)
  52. pm/polymode config
  53. pm/chunkmode hostmode
  54. pm/current t
  55. pm/type nil)
  56. (pm--common-setup)
  57. ;; Initialize innermodes
  58. (pm--initialize-innermodes config)
  59. ;; FIXME: must go into polymode-compat.el
  60. (add-hook 'flyspell-incorrect-hook
  61. 'pm--flyspel-dont-highlight-in-chunkmodes nil t))
  62. (pm--run-init-hooks hostmode 'host 'polymode-init-host-hook)))
  63. (defun pm--initialize-innermodes (config)
  64. (let ((inner-syms (delete-dups
  65. (delq :inherit
  66. (apply #'append
  67. (pm--collect-parent-slots
  68. config 'innermodes
  69. (lambda (obj)
  70. (memq :inherit
  71. (eieio-oref obj 'innermodes)))))))))
  72. (oset config -innermodes
  73. (mapcar (lambda (sub-name)
  74. (clone (symbol-value sub-name)))
  75. inner-syms))))
  76. (cl-defmethod pm-initialize ((chunkmode pm-inner-chunkmode) &optional type mode)
  77. "Initialization of the innermodes' (indirect) buffers."
  78. ;; run in chunkmode indirect buffer
  79. (setq mode (or mode (pm--get-innermode-mode chunkmode type)))
  80. (let* ((pm-initialization-in-progress t)
  81. (post-fix (replace-regexp-in-string "poly-\\|-mode" "" (symbol-name mode)))
  82. (core-name (format "%s[%s]" (buffer-name (pm-base-buffer))
  83. (or (cdr (assoc post-fix polymode-mode-abbrev-aliases))
  84. post-fix)))
  85. (new-name (generate-new-buffer-name core-name)))
  86. (rename-buffer new-name)
  87. (pm--mode-setup mode)
  88. (pm--move-vars '(pm/polymode buffer-file-coding-system) (pm-base-buffer))
  89. ;; FIXME: This breaks if different chunkmodes use same-mode buffer. Even for
  90. ;; head/tail the value of pm/type will be wrong for tail
  91. (setq pm--core-buffer-name core-name
  92. pm/chunkmode chunkmode
  93. pm/type (pm-true-span-type chunkmode type))
  94. ;; FIXME: should not be here?
  95. (vc-refresh-state)
  96. (pm--common-setup)
  97. (add-hook 'syntax-propertize-extend-region-functions
  98. #'polymode-syntax-propertize-extend-region-in-host
  99. -90 t)
  100. (pm--move-vars polymode-move-these-vars-from-base-buffer (pm-base-buffer))
  101. ;; If this rename happens before the mode setup font-lock doesn't work in
  102. ;; inner buffers.
  103. (when pm-hide-implementation-buffers
  104. (rename-buffer (generate-new-buffer-name (concat " " pm--core-buffer-name)))))
  105. (pm--run-init-hooks chunkmode type 'polymode-init-inner-hook)
  106. ;; Call polymode mode for the sake of the keymap and hook. Same minor mode
  107. ;; which runs in the host buffer but without recursive call to `pm-initialize'.
  108. (funcall (eieio-oref pm/polymode '-minor-mode)))
  109. (defvar poly-lock-allow-fontification)
  110. (defun pm--mode-setup (mode &optional buffer)
  111. ;; General major-mode install. Should work for both indirect and base buffers.
  112. ;; PM objects are not yet initialized (pm/polymode, pm/chunkmode, pm/type)
  113. (with-current-buffer (or buffer (current-buffer))
  114. ;; don't re-install if already there; polymodes can be used as minor modes.
  115. (unless (eq major-mode mode)
  116. (let ((polymode-mode t) ;major-modes might check this
  117. (base (buffer-base-buffer))
  118. ;; Some modes (or minor-modes which are run in their hooks) call
  119. ;; font-lock functions directly on the entire buffer (#212 for an
  120. ;; example). They were inhibited here before, but these variables
  121. ;; are designed to be set by modes, so our setup doesn't have an
  122. ;; effect in those cases and we get "Making xyz buffer-local while
  123. ;; locally let-bound!" warning which seems to be harmless but
  124. ;; annoying. The only solution seems to be to advice those
  125. ;; functions, particularly `font-lock-fontify-region`.
  126. ;; (font-lock-flush-function 'ignore)
  127. ;; (font-lock-ensure-function 'ignore)
  128. ;; (font-lock-fontify-buffer-function 'ignore)
  129. ;; (font-lock-fontify-region-function 'ignore)
  130. (font-lock-function 'ignore)
  131. ;; Mode functions can do arbitrary things. We inhibt all PM hooks
  132. ;; because PM objects have not been setup yet.
  133. (pm-allow-after-change-hook nil)
  134. (poly-lock-allow-fontification nil))
  135. ;; run-mode-hooks needs buffer-file-name, so we transfer base vars twice
  136. (when base
  137. (pm--move-vars polymode-move-these-vars-from-base-buffer base))
  138. (condition-case-unless-debug err
  139. ;; !! run-mode-hooks and hack-local-variables run here
  140. (funcall mode)
  141. (error (message "Polymode error (pm--mode-setup '%s): %s"
  142. mode (error-message-string err))))
  143. ;; In emacs 27 this is called from run-mode-hooks
  144. (and (bound-and-true-p syntax-propertize-function)
  145. (not (local-variable-p 'parse-sexp-lookup-properties))
  146. (setq-local parse-sexp-lookup-properties t))))
  147. (setq polymode-mode t)
  148. (current-buffer)))
  149. (defvar syntax-ppss-wide)
  150. (defun pm--common-setup (&optional buffer)
  151. "Run common setup in BUFFER.
  152. Runs after major mode and core polymode structures have been
  153. initialized. Return the buffer."
  154. (with-current-buffer (or buffer (current-buffer))
  155. (object-add-to-list pm/polymode '-buffers (current-buffer))
  156. ;; INDENTATION
  157. (setq-local pm--indent-line-function-original
  158. (if (memq indent-line-function '(indent-relative indent-relative-maybe))
  159. #'pm--indent-line-basic
  160. indent-line-function))
  161. (setq-local indent-line-function #'pm-indent-line-dispatcher)
  162. (setq-local pm--indent-region-function-original
  163. (if (memq indent-region-function '(nil indent-region-line-by-line))
  164. #'pm--indent-region-line-by-line
  165. indent-region-function))
  166. (setq-local indent-region-function #'pm-indent-region)
  167. ;; FILL
  168. (setq-local pm--fill-forward-paragraph-original fill-forward-paragraph-function)
  169. (setq-local fill-forward-paragraph-function #'polymode-fill-forward-paragraph)
  170. ;; HOOKS
  171. (add-hook 'kill-buffer-hook #'polymode-after-kill-fixes nil t)
  172. (add-hook 'post-command-hook #'polymode-post-command-select-buffer nil t)
  173. (add-hook 'pre-command-hook #'polymode-pre-command-synchronize-state nil t)
  174. ;; FONT LOCK (see poly-lock.el)
  175. (setq-local font-lock-function 'poly-lock-mode)
  176. ;; Font lock is a globalized minor mode and is thus initialized in
  177. ;; `after-change-major-mode-hook' within `run-mode-hooks'. As a result
  178. ;; poly-lock won't get installed if polymode is installed as a minor mode or
  179. ;; interactively. We add font/poly-lock in all buffers (because this is how
  180. ;; inner buffers are installed) but use `poly-lock-allow-fontification' to
  181. ;; disallow fontification in buffers which don't want font-lock (aka those
  182. ;; buffers where `turn-on-font-lock-if-desired' doesn't activate font-lock).
  183. (turn-on-font-lock-if-desired) ; <- need this for the sake of poly-minor-modes
  184. ;; FIXME: can poly-lock-mode be used here instead?
  185. (setq-local poly-lock-allow-fontification font-lock-mode)
  186. ;; Make sure to re-install with our font-lock-function as
  187. ;; `turn-on-font-lock-if-desired' from above might actually not call it.
  188. (font-lock-mode t)
  189. (font-lock-flush)
  190. ;; SYNTAX (must be done after font-lock for after-change order)
  191. (with-no-warnings
  192. ;; [OBSOLETE as of 25.1 but we still protect it]
  193. (pm-around-advice syntax-begin-function 'pm-override-output-position))
  194. ;; (advice-remove 'c-beginning-of-syntax #'pm-override-output-position)
  195. ;; Ideally this should be called in some hook to avoid minor-modes messing
  196. ;; it up. Setting even if syntax-propertize-function is nil to have more
  197. ;; control over syntax-propertize--done.
  198. (unless (eq syntax-propertize-function #'polymode-syntax-propertize)
  199. (setq-local pm--syntax-propertize-function-original syntax-propertize-function)
  200. (setq-local syntax-propertize-function #'polymode-syntax-propertize))
  201. (setq-local syntax-ppss-wide (cons nil nil))
  202. ;; Flush ppss in all buffers. Must be done in first after-change (see
  203. ;; https://lists.gnu.org/archive/html/emacs-devel/2019-03/msg00500.html)
  204. ;; TODO: Consider just advising syntax-ppss-flush-cache once the above is
  205. ;; fixed in emacs.
  206. (add-hook 'after-change-functions 'polymode-flush-syntax-ppss-cache nil t)
  207. (current-buffer)))
  208. ;;; BUFFER CREATION
  209. (cl-defgeneric pm-get-buffer-create (chunkmode &optional type)
  210. "Get the indirect buffer associated with SUBMODE and SPAN-TYPE.
  211. Create and initialize the buffer if does not exist yet.")
  212. (cl-defmethod pm-get-buffer-create ((chunkmode pm-host-chunkmode) &optional type)
  213. (when type
  214. (error "Cannot create host buffer of type '%s'" type))
  215. (let ((buff (eieio-oref chunkmode '-buffer)))
  216. (if (buffer-live-p buff)
  217. buff
  218. (error "Cannot create host buffer for host chunkmode %s" (eieio-object-name chunkmode)))))
  219. (cl-defmethod pm-get-buffer-create ((chunkmode pm-inner-chunkmode) &optional type)
  220. (let ((buff (cl-case type
  221. (body (eieio-oref chunkmode '-buffer))
  222. (head (eieio-oref chunkmode '-head-buffer))
  223. (tail (eieio-oref chunkmode '-tail-buffer))
  224. (t (error "Don't know how to select buffer of type '%s' for chunkmode '%s'"
  225. type (eieio-object-name chunkmode))))))
  226. (if (buffer-live-p buff)
  227. buff
  228. (let ((new-buff (pm--get-innermode-buffer-create chunkmode type)))
  229. (pm--set-innermode-buffer chunkmode type new-buff)))))
  230. (defun pm--get-innermode-buffer-create (chunkmode type &optional force-new)
  231. (let ((mode (pm--get-innermode-mode chunkmode type)))
  232. (or
  233. ;; 1. search through the existing buffer list
  234. (unless force-new
  235. (cl-loop for bf in (eieio-oref pm/polymode '-buffers)
  236. when (let ((out (and (buffer-live-p bf)
  237. (eq mode (buffer-local-value 'major-mode bf)))))
  238. out)
  239. return bf))
  240. ;; 2. create new
  241. (with-current-buffer (pm-base-buffer)
  242. (let* ((new-name (generate-new-buffer-name (buffer-name)))
  243. (new-buffer (make-indirect-buffer (current-buffer) new-name)))
  244. (with-current-buffer new-buffer
  245. (pm-initialize chunkmode type mode))
  246. new-buffer)))))
  247. (defun pm-get-buffer-of-mode (mode)
  248. (let ((mode (pm--true-mode-symbol mode)))
  249. (or
  250. ;; 1. search through the existing buffer list
  251. (cl-loop for bf in (eieio-oref pm/polymode '-buffers)
  252. when (and (buffer-live-p bf)
  253. (eq mode (buffer-local-value 'major-mode bf)))
  254. return bf)
  255. ;; 2. create new if body mode matched
  256. (cl-loop for imode in (eieio-oref pm/polymode '-innermodes)
  257. when (eq mode (eieio-oref imode 'mode))
  258. return (pm--get-innermode-buffer-create imode 'body 'force)))))
  259. (defun pm--set-innermode-buffer (obj type buff)
  260. "Assign BUFF to OBJ's slot(s) corresponding to TYPE."
  261. (with-slots (-buffer head-mode -head-buffer tail-mode -tail-buffer) obj
  262. (pcase (list type head-mode tail-mode)
  263. (`(body body ,(or `nil `body))
  264. (setq -buffer buff
  265. -head-buffer buff
  266. -tail-buffer buff))
  267. (`(body ,_ body)
  268. (setq -buffer buff
  269. -tail-buffer buff))
  270. (`(body ,_ ,_ )
  271. (setq -buffer buff))
  272. (`(head ,_ ,(or `nil `head))
  273. (setq -head-buffer buff
  274. -tail-buffer buff))
  275. (`(head ,_ ,_)
  276. (setq -head-buffer buff))
  277. (`(tail ,_ ,(or `nil `head))
  278. (setq -tail-buffer buff
  279. -head-buffer buff))
  280. (`(tail ,_ ,_)
  281. (setq -tail-buffer buff))
  282. (_ (error "Type must be one of 'body, 'head or 'tail")))))
  283. ;;; SPAN MANIPULATION
  284. (cl-defgeneric pm-get-span (chunkmode &optional pos)
  285. "Ask the CHUNKMODE for the span at point.
  286. Return a list of three elements (TYPE BEG END OBJECT) where TYPE
  287. is a symbol representing the type of the span surrounding
  288. POS (head, tail, body). BEG and END are the coordinates of the
  289. span. OBJECT is a suitable object which is 'responsible' for this
  290. span. This is an object that could be dispatched upon with
  291. `pm-select-buffer'. Should return nil if there is no SUBMODE
  292. specific span around POS. Not to be used in programs directly;
  293. use `pm-innermost-span'.")
  294. (cl-defmethod pm-get-span (chunkmode &optional _pos)
  295. "Return nil.
  296. Host modes usually do not compute spans."
  297. (unless chunkmode
  298. (error "Dispatching `pm-get-span' on a nil object"))
  299. nil)
  300. (cl-defmethod pm-get-span ((chunkmode pm-inner-chunkmode) &optional pos)
  301. "Return a list of the form (TYPE POS-START POS-END SELF).
  302. TYPE can be 'body, 'head or 'tail. SELF is the CHUNKMODE."
  303. (with-slots (head-matcher tail-matcher head-mode tail-mode) chunkmode
  304. (let ((span (pm--span-at-point head-matcher tail-matcher pos
  305. (eieio-oref chunkmode 'can-overlap))))
  306. (when span
  307. (append span (list chunkmode))))))
  308. (cl-defmethod pm-get-span ((_chunkmode pm-inner-auto-chunkmode) &optional _pos)
  309. (let ((span (cl-call-next-method)))
  310. (if (null (car span))
  311. span
  312. (setf (nth 3 span) (apply #'pm--get-auto-chunkmode span))
  313. span)))
  314. ;; (defun pm-get-chunk (ichunkmode &optional pos)
  315. ;; (with-slots (head-matcher tail-matcher head-mode tail-mode) ichunkmode
  316. ;; (pm--span-at-point
  317. ;; head-matcher tail-matcher (or pos (point))
  318. ;; (eieio-oref ichunkmode 'can-overlap)
  319. ;; t)))
  320. (cl-defgeneric pm-next-chunk (chunkmode &optional pos)
  321. "Ask the CHUNKMODE for the chunk after POS.
  322. Return a list of three elements (CHUNKMODE HEAD-BEG HEAD-END
  323. TAIL-BEG TAIL-END).")
  324. (cl-defmethod pm-next-chunk (_chunkmode &optional _pos)
  325. nil)
  326. (cl-defmethod pm-next-chunk ((chunkmode pm-inner-chunkmode) &optional pos)
  327. (with-slots (head-matcher tail-matcher head-mode tail-mode) chunkmode
  328. (let ((raw-chunk (pm--next-chunk
  329. head-matcher tail-matcher (or pos (point))
  330. (eieio-oref chunkmode 'can-overlap))))
  331. (when raw-chunk
  332. (cons chunkmode raw-chunk)))))
  333. (cl-defmethod pm-next-chunk ((chunkmode pm-inner-auto-chunkmode) &optional pos)
  334. (with-slots (head-matcher tail-matcher head-mode tail-mode) chunkmode
  335. (let ((raw-chunk (pm--next-chunk
  336. head-matcher tail-matcher (or pos (point))
  337. (eieio-oref chunkmode 'can-overlap))))
  338. (when raw-chunk
  339. (cons (pm--get-auto-chunkmode 'head (car raw-chunk) (cadr raw-chunk) chunkmode)
  340. raw-chunk)))))
  341. ;; FIXME: cache somehow?
  342. (defun pm--get-auto-chunkmode (type beg end proto)
  343. (save-excursion
  344. (goto-char beg)
  345. (unless (eq type 'head)
  346. (goto-char end) ; fixme: add multiline matchers to micro-optimize this
  347. (let ((matcher (pm-fun-matcher (eieio-oref proto 'head-matcher))))
  348. ;; can be multiple incomplete spans within a span
  349. (while (< beg (goto-char (car (funcall matcher -1)))))))
  350. (let* ((str (let ((matcher (eieio-oref proto 'mode-matcher)))
  351. (when (stringp matcher)
  352. (setq matcher (cons matcher 0)))
  353. (cond ((consp matcher)
  354. (re-search-forward (car matcher) (point-at-eol) t)
  355. (match-string-no-properties (cdr matcher)))
  356. ((functionp matcher)
  357. (funcall matcher)))))
  358. (mode (pm-get-mode-symbol-from-name str (eieio-oref proto 'fallback-mode))))
  359. (if (eq mode 'host)
  360. (oref pm/polymode -hostmode)
  361. ;; chunkname:MODE serves as ID (e.g. `markdown-fenced-code:emacs-lisp-mode`).
  362. ;; Head/tail/body indirect buffers are shared across chunkmodes and span
  363. ;; types.
  364. (let ((automodes (eieio-oref pm/polymode '-auto-innermodes)))
  365. (if (memq proto automodes)
  366. ;; a. if proto already part of the list return
  367. proto
  368. (let ((name (concat (pm-object-name proto) ":" (symbol-name mode))))
  369. (or
  370. ;; b. loop through installed inner modes
  371. (cl-loop for obj in automodes
  372. when (equal name (pm-object-name obj))
  373. return obj)
  374. ;; c. create new
  375. (let ((innermode (clone proto :name name :mode mode)))
  376. (object-add-to-list pm/polymode '-auto-innermodes innermode)
  377. innermode)))))))))
  378. ;;; INDENT
  379. ;; indent-region-line-by-line for polymode buffers (more efficient, works on
  380. ;; emacs 25, but no progress reporter)
  381. (defun pm--indent-region-line-by-line (start end)
  382. (save-excursion
  383. ;; called from pm--indent-raw; so we know we are in the same span with
  384. ;; buffer set and narrowed to span if 'protect-indent is non-nil
  385. (let ((span (pm-innermost-span start)))
  386. (setq end (copy-marker end))
  387. (goto-char start)
  388. (while (< (point) end)
  389. (unless (and (bolp) (eolp))
  390. ;; fixme: html-erb jumps line here; need save-excursion. why?
  391. (save-excursion (pm-indent-line (nth 3 span) span)))
  392. (forward-line 1))
  393. (move-marker end nil))))
  394. (defun pm--indent-line-basic ()
  395. "Used as `indent-line-function' for modes with tab indent."
  396. ;; adapted from indent-according-to-mode
  397. (let ((column (save-excursion
  398. (beginning-of-line)
  399. (if (bobp) 0
  400. (beginning-of-line 0)
  401. (if (looking-at "[ \t]*$") 0 (current-indentation))))))
  402. (if (<= (current-column) (current-indentation))
  403. (indent-line-to column)
  404. (save-excursion (indent-line-to column)))))
  405. (defun pm--indent-raw (span fn-sym &rest args)
  406. ;; fixme: do save-excursion instead of this?
  407. (let ((point (point)))
  408. ;; do fast synchronization here
  409. (save-current-buffer
  410. (pm-set-buffer span)
  411. (goto-char point)
  412. (let ((fn (symbol-value fn-sym)))
  413. (when fn
  414. (if (eieio-oref (nth 3 span) 'protect-indent)
  415. (pm-with-narrowed-to-span span
  416. (apply fn args))
  417. (apply fn args))))
  418. (setq point (point)))
  419. (goto-char point)))
  420. (defun pm--indent-line-raw (span)
  421. (pm--indent-raw span 'pm--indent-line-function-original)
  422. (pm--reindent-with+-indent span (point-at-bol) (point-at-eol)))
  423. (defun pm--indent-region-raw (span beg end)
  424. (pm--indent-raw span 'pm--indent-region-function-original beg end)
  425. (pm--reindent-with+-indent span beg end))
  426. (defun pm-indent-region (beg end)
  427. "Indent region between BEG and END in polymode buffers.
  428. Function used for `indent-region-function'."
  429. ;; (message "(pm-indent-region %d %d)" beg end)
  430. ;; cannot use pm-map-over-spans here because of the buffer modifications
  431. (let ((inhibit-point-motion-hooks t)
  432. (end (copy-marker end)))
  433. (save-excursion
  434. (while (< beg end)
  435. (goto-char beg)
  436. (back-to-indentation)
  437. (setq beg (point))
  438. (let ((span (pm-innermost-span beg 'no-cache)))
  439. (let* ((end-span (copy-marker (nth 2 span)))
  440. (end1 (min end end-span)))
  441. (goto-char beg)
  442. ;; (pm-switch-to-buffer)
  443. ;; indent first line separately
  444. (pm-indent-line (nth 3 span) span)
  445. (beginning-of-line 2)
  446. (when (< (point) end1)
  447. ;; we know that span end was moved, hard reset without recomputation
  448. (setf (nth 2 span) end-span)
  449. (pm--indent-region-raw span (point) end1))
  450. (setq beg (max end1 (point)))))))
  451. (move-marker end nil)))
  452. (defun pm-indent-line-dispatcher (&optional span)
  453. "Dispatch `pm-indent-line' methods on current SPAN.
  454. Value of `indent-line-function' in polymode buffers."
  455. ;; NB: No buffer switching in indentation functions. See comment at
  456. ;; pm-switch-to-buffer.
  457. (let ((span (or span (pm-innermost-span
  458. (save-excursion (back-to-indentation) (point)))))
  459. (inhibit-read-only t))
  460. (pm-indent-line (nth 3 span) span)))
  461. (cl-defgeneric pm-indent-line (chunkmode &optional span)
  462. "Indent current line.
  463. Protect and call original indentation function associated with
  464. the chunkmode.")
  465. (cl-defmethod pm-indent-line ((_chunkmode pm-chunkmode) span)
  466. (let ((pos (point))
  467. (delta))
  468. (back-to-indentation)
  469. (setq delta (- pos (point)))
  470. (let* ((bol (point-at-bol))
  471. (span (or span (pm-innermost-span)))
  472. (prev-span-pos)
  473. (first-line (save-excursion
  474. (goto-char (nth 1 span))
  475. (unless (bobp)
  476. (setq prev-span-pos (1- (point))))
  477. (forward-line)
  478. (<= bol (point)))))
  479. (pm--indent-line-raw span)
  480. (when (and first-line prev-span-pos)
  481. (pm--reindent-with-extra-offset (pm-innermost-span prev-span-pos)
  482. 'post-indent-offset)))
  483. (when (and delta (> delta 0))
  484. (goto-char (+ (point) delta)))))
  485. (cl-defmethod pm-indent-line ((_chunkmode pm-inner-chunkmode) span)
  486. "Indent line in inner chunkmodes.
  487. When point is at the beginning of head or tail, use parent chunk
  488. to indent."
  489. (let ((pos (point))
  490. (delta))
  491. (back-to-indentation)
  492. (setq delta (- pos (point)))
  493. (unwind-protect
  494. (cond
  495. ;; 1. HEAD or TAIL (we assume head or tail fits in one line for now)
  496. ((or (eq 'head (car span))
  497. (eq 'tail (car span)))
  498. (goto-char (nth 1 span))
  499. (when (not (bobp))
  500. ;; ind-point need not be in prev-span; there might be other spans in between
  501. (let ((prev-span (pm-innermost-span (1- (point)))))
  502. (if (eq 'tail (car span))
  503. (indent-line-to (pm--head-indent prev-span))
  504. ;; head indent and adjustments
  505. ;; (pm-indent-line (nth 3 prev-span) prev-span)
  506. (pm--indent-line-raw prev-span)
  507. (let ((prev-tail-pos (save-excursion
  508. (beginning-of-line)
  509. (skip-chars-backward " \t\n")
  510. (if (bobp) (point) (1- (point))))))
  511. (setq prev-span (pm-innermost-span prev-tail-pos)))
  512. (pm--reindent-with-extra-offset prev-span 'post-indent-offset)
  513. (pm--reindent-with-extra-offset span 'pre-indent-offset)))))
  514. ;; 2. BODY
  515. (t
  516. (if (< (point) (nth 1 span))
  517. ;; first body line in the same line with header (re-indent at indentation)
  518. (pm-indent-line-dispatcher)
  519. (let ((fl-indent (pm--first-line-indent span)))
  520. (if fl-indent
  521. ;; We are not on the 1st line
  522. (progn
  523. ;; thus indent according to mode
  524. (pm--indent-line-raw span)
  525. (when (bolp)
  526. ;; When original mode's indented to bol, match with the
  527. ;; first line indent. Otherwise it's a continuation
  528. ;; indentation and we assume the original function did it
  529. ;; correctly with respect to previous lines.
  530. (indent-to fl-indent)))
  531. ;; On the first line. Indent with respect to header line.
  532. (let ((delta (save-excursion
  533. (goto-char (nth 1 span))
  534. (+
  535. (pm--oref-value (nth 3 span) 'body-indent-offset)
  536. (cond
  537. ;; empty line
  538. ((looking-at-p "[ \t]*$") 0)
  539. ;; inner span starts at bol; honor +-indent cookie
  540. ((= (point) (point-at-bol))
  541. (pm--+-indent-offset-on-this-line span))
  542. ;; code after header
  543. (t
  544. (end-of-line)
  545. (skip-chars-forward "\t\n")
  546. (pm--indent-line-raw span)
  547. (- (point) (point-at-bol))))))))
  548. (indent-line-to
  549. ;; indent with respect to header line
  550. (+ delta (pm--head-indent span)))))))))
  551. ;; keep point on same characters
  552. (when (and delta (> delta 0))
  553. (goto-char (+ (point) delta))))))
  554. (defun pm--first-line-indent (&optional span)
  555. (save-excursion
  556. (let ((pos (point)))
  557. (goto-char (nth 1 (or span (pm-innermost-span))))
  558. ;; when body starts at bol move to previous line
  559. (when (and (= (point) (point-at-bol))
  560. (not (bobp)))
  561. (backward-char 1))
  562. (skip-chars-forward " \t\n")
  563. (when (< (point-at-eol) pos)
  564. (- (point) (point-at-bol))))))
  565. ;; SPAN is a body span; do nothing if narrowed to body
  566. (defun pm--head-indent (&optional span)
  567. (save-restriction
  568. (widen)
  569. (save-excursion
  570. (let ((sbeg (nth 1 (or span (pm-innermost-span)))))
  571. (goto-char sbeg)
  572. (backward-char 1)
  573. (let ((head-span (pm-innermost-span)))
  574. (if (eq (car head-span) 'head)
  575. (goto-char (nth 1 head-span))
  576. ;; body span is not preceded by a head span. We don't have such
  577. ;; practical cases yet, but headless spans are real - indented blocks
  578. ;; for instance.
  579. (goto-char sbeg)))
  580. (back-to-indentation)
  581. (- (point) (point-at-bol))))))
  582. (defun pm--+-indent-offset-on-this-line (span)
  583. (if (re-search-forward "\\([+-]\\)indent" (point-at-eol) t)
  584. (let ((basic-offset (pm--oref-value (nth 3 span) 'indent-offset)))
  585. (if (string= (match-string 1) "-")
  586. (- basic-offset)
  587. basic-offset))
  588. 0))
  589. (defun pm--reindent-with+-indent (span beg end)
  590. (save-excursion
  591. (goto-char beg)
  592. (let ((basic-offset (pm--oref-value (nth 3 span) 'indent-offset)))
  593. (while (and (< (point) end)
  594. (re-search-forward "\\([+-]\\)indent" end t))
  595. (let ((offset (if (string= (match-string 1) "-")
  596. (- basic-offset)
  597. basic-offset)))
  598. (indent-line-to (max 0 (+ (current-indentation) offset)))
  599. (forward-line))))))
  600. (defun pm--reindent-with-extra-offset (span offset-type &optional offset2)
  601. (let ((offset (eieio-oref (nth 3 span) offset-type)))
  602. (unless (and (numberp offset) (= offset 0))
  603. (let ((pos (nth (if (eq offset-type 'post-indent-offset) 2 1) span)))
  604. (save-excursion
  605. (goto-char pos)
  606. (setq offset (pm--object-value offset)))
  607. (indent-line-to (max 0 (+ (current-indentation) offset (or offset2 0))))))))
  608. ;;; FACES
  609. (cl-defgeneric pm-get-adjust-face (chunkmode type))
  610. (cl-defmethod pm-get-adjust-face ((chunkmode pm-chunkmode) _type)
  611. (eieio-oref chunkmode 'adjust-face))
  612. (cl-defmethod pm-get-adjust-face ((chunkmode pm-inner-chunkmode) type)
  613. (cond ((eq type 'head)
  614. (eieio-oref chunkmode 'head-adjust-face))
  615. ((eq type 'tail)
  616. (or (eieio-oref chunkmode 'tail-adjust-face)
  617. (eieio-oref chunkmode 'head-adjust-face)))
  618. (t (eieio-oref chunkmode 'adjust-face))))
  619. (provide 'polymode-methods)
  620. ;;; polymode-methods.el ends here