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.

2139 lines
89 KiB

  1. ;; polymode-core.el --- Core initialization and utilities for polymode -*- 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 'gv)
  30. (require 'font-lock)
  31. (require 'color)
  32. (require 'polymode-classes)
  33. (require 'format-spec)
  34. (require 'subr-x)
  35. (eval-when-compile
  36. (require 'cl-lib)
  37. (require 'derived))
  38. ;;; ESSENTIAL DECLARATIONS
  39. (defvar *span* nil)
  40. (defvar-local pm/polymode nil)
  41. (put 'pm/polymode 'permanent-local t)
  42. (defvar-local pm/chunkmode nil)
  43. (defvar-local pm/current nil) ;; fixme: unused
  44. (defvar-local pm/type nil) ;; fixme: remove this
  45. (defvar-local polymode-mode nil
  46. "Non-nil if current \"mode\" is a polymode.")
  47. (defvar pm--emacs>26 (version<= "26" emacs-version))
  48. ;; overwrites
  49. (defvar-local pm--indent-region-function-original nil)
  50. (defvar-local pm--fill-forward-paragraph-original nil)
  51. (defvar-local pm--indent-line-function-original nil)
  52. (defvar-local pm--syntax-propertize-function-original nil)
  53. ;; silence the compiler
  54. (defvar pm--output-file nil)
  55. (defvar pm--input-buffer nil)
  56. (defvar pm--input-file nil)
  57. (defvar pm--export-spec nil)
  58. (defvar pm--input-not-real nil)
  59. (defvar pm--output-not-real nil)
  60. ;; methods api from polymode-methods.el
  61. (declare-function pm-initialize "polymode-methods")
  62. (declare-function pm-get-buffer-of-mode "polymode-methods")
  63. (declare-function pm-get-buffer-create "polymode-methods")
  64. (declare-function pm-get-adjust-face "polymode-methods")
  65. (declare-function pm-get-span "polymode-methods")
  66. (declare-function pm-next-chunk "polymode-methods")
  67. ;; eieio silence "unknown slot"
  68. ;; http://emacs.1067599.n8.nabble.com/Fixing-quot-Unknown-slot-quot-warnings-td419119.html
  69. (eval-when-compile
  70. (defclass dummy ()
  71. ((function) (from-to))))
  72. (defun pm-object-name (obj)
  73. ;; gives warnings on e25,26 but fine in e27
  74. (with-no-warnings
  75. (eieio-object-name-string obj)))
  76. ;; SHIELDS
  77. (defvar pm-allow-after-change-hook t)
  78. (defvar pm-allow-post-command-hook t)
  79. (defun polymode-disable-post-command ()
  80. (when polymode-mode
  81. (setq pm-allow-post-command-hook nil)))
  82. (defun polymode-enable-post-command ()
  83. (when polymode-mode
  84. (setq pm-allow-post-command-hook t)))
  85. ;; We need this during cascaded call-next-method in pm-initialize. -innermodes
  86. ;; are initialized after the hostmode setup has taken place. This means that
  87. ;; pm-get-span and all the functionality that relies on it will fail to work
  88. ;; correctly during the initialization in the call-next-method. This is
  89. ;; particularly relevant to font-lock setup and user hooks.
  90. (defvar pm-initialization-in-progress nil)
  91. (defvar pm-hide-implementation-buffers t)
  92. (defvar-local pm--core-buffer-name nil)
  93. (defun pm--hidden-buffer-name ()
  94. (generate-new-buffer-name (concat " " pm--core-buffer-name)))
  95. (defun pm--visible-buffer-name ()
  96. (generate-new-buffer-name
  97. (replace-regexp-in-string "^ +" "" pm--core-buffer-name)))
  98. ;;; CUSTOM
  99. ;;;###autoload
  100. (defvar-local polymode-default-inner-mode nil
  101. "Inner mode for chunks with unspecified modes.
  102. Intended to be used as local variable in polymode buffers. A
  103. special value 'host means use the host mode.")
  104. ;;;###autoload
  105. (put 'polymode-default-inner-mode 'safe-local-variable 'symbolp)
  106. (defgroup polymode nil
  107. "Object oriented framework for multiple modes based on indirect buffers"
  108. :link '(emacs-commentary-link "polymode")
  109. :group 'tools)
  110. (defgroup poly-modes nil
  111. "Polymode Configuration Objects"
  112. :group 'polymode)
  113. (defgroup poly-hostmodes nil
  114. "Polymode Host Chunkmode Objects"
  115. :group 'polymode)
  116. (defgroup poly-innermodes nil
  117. "Polymode Chunkmode Objects"
  118. :group 'polymode)
  119. (defcustom polymode-display-output-file t
  120. "Whether to display woven and exported output buffers.
  121. When non-nil automatically visit and call `display-buffer' on
  122. output files from processor engines (e.g. weavers and exporters).
  123. Can also be a function, in which case it is called with the
  124. output file name as the only argument. If this function returns
  125. non-nil, the file is visited and displayed with `display-buffer'.
  126. See `display-buffer-alist' for how to customize the display."
  127. :group 'polymode
  128. :type '(choice (const t) (const nil) function))
  129. (defcustom polymode-display-process-buffers t
  130. "When non-nil, display weaving and exporting process buffers."
  131. :group 'polymode
  132. :type 'boolean)
  133. (defcustom polymode-skip-processing-when-unmodified t
  134. "If non-nil, consider modification times of input and output files.
  135. Skip weaving or exporting process when output file is more recent
  136. than the input file."
  137. :group 'polymode
  138. :type 'boolean)
  139. (define-obsolete-variable-alias 'polymode-mode-name-override-alist 'polymode-mode-name-aliases "2018-08")
  140. (define-obsolete-variable-alias 'polymode-mode-name-alias-alist 'polymode-mode-name-aliases "2019-04")
  141. (defcustom polymode-mode-name-aliases
  142. '((elisp . emacs-lisp)
  143. (el . emacs-lisp)
  144. (bash . sh-mode))
  145. "An alist of inner mode overrides.
  146. When inner mode is automatically detected from the header of the
  147. inner chunk (such as in markdown mode), the detected symbol might
  148. not correspond to the desired mode. This alist maps discovered
  149. symbols into desired modes. For example
  150. (add-to-list 'polymode-mode-name-aliases '(julia . ess-julia))
  151. will cause installation of `ess-julia-mode' in markdown ```julia chunks."
  152. :group 'polymode
  153. :type 'alist)
  154. (defvar polymode-mode-abbrev-aliases nil
  155. "An alist of abbreviation mappings from mode names to their abbreviations.
  156. Used to compute mode post-fixes in buffer names. Example:
  157. (add-to-list 'polymode-mode-abbrevs-aliases '(\"ess-r\" . \"R\"))")
  158. (defvar polymode-before-switch-buffer-hook nil
  159. "Hook run just before switching to a different polymode buffer.
  160. Each function is run with two arguments `old-buffer' and
  161. `new-buffer'. This hook is commonly used to transfer state
  162. between buffers. Hook is run before transfer of variables, modes
  163. and overlays.")
  164. (define-obsolete-variable-alias 'polymode-switch-buffer-hook 'polymode-after-switch-buffer-hook "v0.2")
  165. (defvar polymode-after-switch-buffer-hook nil
  166. "Hook run after switching to a different polymode buffer.
  167. Each function is run with two arguments `old-buffer' and
  168. `new-buffer'. This hook is commonly used to transfer state
  169. between buffers. Slot :switch-buffer-functions in `pm-polymode'
  170. and `pm-chunkmode' objects provides same functionality for
  171. narrower scope.")
  172. (defvar polymode-init-host-hook nil
  173. "Hook run on initialization of every hostmode.
  174. Ran in a base buffer from `pm-initialze'
  175. methods. Slot :init-functions in `pm-polymode' objects provides
  176. similar hook for more focused scope. See
  177. `polymode-init-inner-hook' and :init-functions slot in
  178. `pm-chunkmode' objects for similar hooks for inner chunkmodes.")
  179. (defvar polymode-init-inner-hook nil
  180. "Hook run on initialization of every `pm-chunkmode' object.
  181. The hook is run in chunkmode's body buffer from `pm-initialze'
  182. `pm-chunkmode' methods. Slot :init-functions `pm-chunkmode'
  183. objects provides same functionality for narrower scope. See also
  184. `polymode-init-host-hook'.")
  185. ;;; Mode Macros
  186. (defun polymode--define-chunkmode (constructor name parent doc key-args)
  187. (let* ((type (format "%smode"
  188. (replace-regexp-in-string
  189. "-.*$" "" (replace-regexp-in-string "^pm-" "" (symbol-name constructor)))))
  190. (sname (symbol-name name))
  191. (root-name (replace-regexp-in-string (format "poly-\\|-%s" type) "" sname)))
  192. (when (keywordp parent)
  193. (progn
  194. (push doc key-args)
  195. (push parent key-args)
  196. (setq doc nil parent nil)))
  197. (unless (stringp doc)
  198. (when (keywordp doc)
  199. (push doc key-args))
  200. (setq doc (format "%s for %s chunks." (capitalize type) root-name)))
  201. (unless (string-match-p (format "-%s$" type) sname)
  202. (error "%s must end in '-%s'" (capitalize type) type))
  203. (unless (symbolp parent)
  204. ;; fixme: check inheritance
  205. (error "PARENT must be a name of an `%s'" type))
  206. `(progn
  207. (makunbound ',name)
  208. (defvar ,name
  209. ,(if parent
  210. `(pm--safe-clone ',constructor ,parent :name ,root-name ,@key-args)
  211. `(,constructor :name ,root-name ,@key-args))
  212. ,doc))
  213. ;; `(progn
  214. ;; (defvar ,name)
  215. ;; (defcustom ,name nil
  216. ;; ,doc
  217. ;; :group ',(intern (format "poly-%ss" type))
  218. ;; :type 'object)
  219. ;; (setq ,name
  220. ;; ,(if parent
  221. ;; `(clone ,parent :name ,root-name ,@key-args)
  222. ;; `(,constructor :name ,root-name ,@key-args))))
  223. ))
  224. ;;;###autoload
  225. (defmacro define-hostmode (name &optional parent doc &rest key-args)
  226. "Define a hostmode with name NAME.
  227. Optional PARENT is a name of a hostmode to be derived (cloned)
  228. from. If missing, the optional documentation string DOC is
  229. generated automatically. KEY-ARGS is a list of key-value pairs.
  230. See the documentation of the class `pm-host-chunkmode' for
  231. possible values."
  232. (declare (doc-string 3))
  233. (polymode--define-chunkmode 'pm-host-chunkmode name parent doc key-args))
  234. ;;;###autoload
  235. (defmacro define-innermode (name &optional parent doc &rest key-args)
  236. "Ddefine an innermode with name NAME.
  237. Optional PARENT is a name of a innermode to be derived (cloned)
  238. from. If missing the optional documentation string DOC is
  239. generated automatically. KEY-ARGS is a list of key-value pairs.
  240. See the documentation of the class `pm-inner-chunkmode' for
  241. possible values."
  242. (declare (doc-string 3))
  243. (polymode--define-chunkmode 'pm-inner-chunkmode name parent doc key-args))
  244. ;;;###autoload
  245. (defmacro define-auto-innermode (name &optional parent doc &rest key-args)
  246. "Ddefine an auto innermode with name NAME.
  247. Optional PARENT is a name of an auto innermode to be
  248. derived (cloned) from. If missing the optional documentation
  249. string DOC is generated automatically. KEY-ARGS is a list of
  250. key-value pairs. See the documentation of the class
  251. `pm-inner-auto-chunkmode' for possible values."
  252. (declare (doc-string 3))
  253. (polymode--define-chunkmode 'pm-inner-auto-chunkmode name parent doc key-args))
  254. ;;; MESSAGES
  255. (defvar pm-extra-span-info nil)
  256. (defun pm-format-span (&optional span prefixp)
  257. (let* ((span (cond
  258. ((number-or-marker-p span) (pm-innermost-span span))
  259. ((null span) (pm-innermost-span))
  260. (span)))
  261. (message-log-max nil)
  262. (beg (nth 1 span))
  263. (end (nth 2 span))
  264. (type (and span (or (car span) 'host)))
  265. (oname (if span
  266. (eieio-object-name (nth 3 span))
  267. (current-buffer)))
  268. (extra (if pm-extra-span-info
  269. (format (if prefixp "%s " " (%s)") pm-extra-span-info)
  270. "")))
  271. (if prefixp
  272. (format "%s[%s %s-%s %s]" extra type beg end oname)
  273. (format "[%s %s-%s %s]%s" type beg end oname extra))))
  274. ;;; SPANS
  275. (defsubst pm-base-buffer ()
  276. "Return base buffer of current buffer, or the current buffer if it's direct."
  277. (or (buffer-base-buffer (current-buffer))
  278. (current-buffer)))
  279. (defun pm-span-mode (&optional span)
  280. "Retrieve the major mode associated with SPAN."
  281. (pm--true-mode-symbol
  282. (buffer-local-value 'major-mode (pm-span-buffer span))))
  283. (defun pm-span-buffer (&optional span)
  284. "Retrieve the buffer associated with SPAN."
  285. (setq span (or span (pm-innermost-span)))
  286. (let* ((chunkmode (nth 3 span))
  287. (type (pm-true-span-type span)))
  288. (if type
  289. (pm-get-buffer-create chunkmode type)
  290. ;; ignore span's chunkmode as inner spans can request host span
  291. (pm-get-buffer-create (oref pm/polymode -hostmode)))))
  292. (defun pm-true-span-type (chunkmode &optional type)
  293. "Retrieve the TYPE of buffer to be installed for CHUNKMODE.
  294. `pm-innermost-span' returns a raw type (head, body or tail) but
  295. the actual type installed depends on the values of :host-mode and
  296. :tail-mode of the CHUNKMODE object. Always return nil if TYPE is
  297. nil (aka a host span). CHUNKMODE could also be a span, in which
  298. case TYPE is ignored."
  299. ;; fixme: this works on inner modes only. Fix naming.
  300. (when (listp chunkmode)
  301. ;; a span
  302. (setq type (car chunkmode)
  303. chunkmode (nth 3 chunkmode)))
  304. (when (object-of-class-p chunkmode 'pm-inner-chunkmode)
  305. (unless (or (null type) (eq type 'host))
  306. (with-slots (mode head-mode tail-mode fallback-mode) chunkmode
  307. (cond ((eq type 'body)
  308. (unless (or (eq mode 'host)
  309. ;; for efficiency don't check if modes are valid
  310. (and (null mode)
  311. (if polymode-default-inner-mode
  312. (eq polymode-default-inner-mode 'host)
  313. (eq fallback-mode 'host))))
  314. 'body))
  315. ((eq type 'head)
  316. (cond ((eq head-mode 'host) nil)
  317. ((eq head-mode 'body) 'body)
  318. (t 'head)))
  319. ((eq type 'tail)
  320. (cond ((eq tail-mode 'host) nil)
  321. ((eq tail-mode 'body) 'body)
  322. (t 'tail)))
  323. (t (error "Type must be one of nil, 'host, 'head, 'tail or 'body")))))))
  324. (defun pm-cache-span (span)
  325. ;; cache span
  326. (unless pm-initialization-in-progress
  327. (with-silent-modifications
  328. ;; (message "caching: %s %s" (car span) (pm-span-to-range span))
  329. (let ((sbeg (nth 1 span))
  330. (send (nth 2 span)))
  331. (put-text-property sbeg send :pm-span span)
  332. (put-text-property sbeg send :pm-mode (pm-span-mode span))))))
  333. (defun pm-flush-span-cache (beg end &optional buffer)
  334. (with-silent-modifications
  335. (remove-list-of-text-properties beg end '(:pm-span) buffer)))
  336. (defun pm--outspan-p (span thespan)
  337. "Non-nil if SPAN outspans THESPAN.
  338. Return non-nil if SPAN contains THESPAN's chunk (strictly from
  339. the front)."
  340. (let ((type (car thespan))
  341. (beg (nth 1 thespan))
  342. (end (nth 2 thespan))
  343. (sbeg (nth 1 span))
  344. (send (nth 2 span)))
  345. ;; The following check is to ensure that the outer span really
  346. ;; spans outside of the entire thespan's chunk (poly-markdown#6)
  347. (and
  348. (< sbeg beg)
  349. (cond
  350. ((eq type 'body)
  351. (and (let ((hspan (pm-get-span (nth 3 thespan) (1- beg))))
  352. (< sbeg (nth 1 hspan)))
  353. ;; Ends might coincide due to eob
  354. (if (< end send)
  355. (let ((tspan (pm-get-span (nth 3 thespan) (1+ end))))
  356. (<= (nth 2 tspan) send))
  357. (= end send))))
  358. ((eq type 'tail)
  359. (let ((bspan (pm-get-span (nth 3 thespan) (1- beg))))
  360. (when (< sbeg (nth 1 bspan))
  361. (let ((hspan (pm-get-span (nth 3 thespan) (1- (nth 1 bspan)))))
  362. (< sbeg (nth 1 hspan))))))
  363. ;; Ends might coincide due to eob
  364. ((eq type 'head)
  365. (if (< end send)
  366. (let ((bspan (pm-get-span (nth 3 thespan) (1+ end))))
  367. (if (< (nth 2 bspan) send)
  368. (let ((tspan (pm-get-span (nth 3 thespan) (1+ (nth 2 bspan)))))
  369. (<= (nth 2 tspan) send))
  370. (= (nth 2 bspan) send)))
  371. (= end send)))))))
  372. (defun pm--intersect-spans (thespan span)
  373. ;; ASSUMPTION: first thespan should be of the form (nil MIN MAX HOSTMODE)
  374. (when span
  375. (let ((allow-nested (eieio-oref (nth 3 span) 'allow-nested))
  376. (is-host (null (car span))))
  377. (cond
  378. ;; 1. nil means host and it can be an intersection of spans returned
  379. ;; by two neighboring inner chunkmodes. When `allow-nested` is
  380. ;; 'always the innermode essentially behaves like the host-mode.
  381. ((or is-host (eq allow-nested 'always))
  382. ;; when span is already an inner span, new host spans are irrelevant
  383. (unless (car thespan)
  384. (setq thespan
  385. (list (unless is-host (car span))
  386. (max (nth 1 span) (nth 1 thespan))
  387. (min (nth 2 span) (nth 2 thespan))
  388. (nth 3 (if is-host thespan span))))))
  389. ;; 2. Inner span
  390. ((and (>= (nth 1 span) (nth 1 thespan))
  391. (<= (nth 2 span) (nth 2 thespan)))
  392. ;; Accepted only nested spans. In case of crossing (incorrect spans),
  393. ;; first span wins.
  394. (when (or (null (car thespan))
  395. (eieio-oref (nth 3 span) 'can-nest))
  396. (setq thespan span)))
  397. ;; 3. Outer span; overwrite previous span if nesting is not allowed.
  398. ;; This case is very hard because it can result in big invalid span
  399. ;; when a head occurs within a inner-chunk. For example $ for inline
  400. ;; latex can occur within R or python. The hard way to fix this would
  401. ;; require non-local information (e.g. checking if outer span's
  402. ;; extremities are within a host span) and still might not be the full
  403. ;; proof solution. Instead, make use of 'allow-nested property.
  404. ((and (eq allow-nested t)
  405. (car thespan) ; span is an inner span
  406. (not (eieio-oref (nth 3 thespan) 'can-nest))
  407. (pm--outspan-p span thespan))
  408. (setq thespan span)))))
  409. thespan)
  410. (defun pm--get-intersected-span (config &optional pos)
  411. ;; fixme: host should be last, to take advantage of the chunkmodes computation?
  412. (let* ((start (point-min))
  413. (end (point-max))
  414. (pos (or pos (point)))
  415. (hostmode (oref config -hostmode))
  416. (chunkmodes (cons hostmode (oref config -innermodes)))
  417. (thespan (list nil start end hostmode)))
  418. (dolist (cm chunkmodes)
  419. ;; Optimization opportunity: this searches till the end of buffer but the
  420. ;; outermost pm-get-span caller has computed a few spans already so we can
  421. ;; pass limits or narrow to pre-computed span.
  422. (setq thespan (pm--intersect-spans thespan (pm-get-span cm pos))))
  423. (unless (and (<= start end) (<= pos end) (>= pos start))
  424. (error "Bad polymode selection: span:%s pos:%s"
  425. (list start end) pos))
  426. (pm-cache-span thespan)
  427. thespan))
  428. (defun pm--chop-span (span beg end)
  429. ;; destructive!
  430. (when (> beg (nth 1 span))
  431. (setcar (cdr span) beg))
  432. (when (< end (nth 2 span))
  433. (setcar (cddr span) end))
  434. span)
  435. (defun pm--innermost-span (config &optional pos)
  436. (let ((pos (or pos (point)))
  437. (omin (point-min))
  438. (omax (point-max))
  439. ;; `re-search-forward' and other search functions trigger full
  440. ;; `internal--syntax-propertize' on the whole buffer on every
  441. ;; single buffer modification. This is a small price to pay for a
  442. ;; much improved efficiency in modes which heavily rely on
  443. ;; `syntax-propertize' like `markdown-mode'.
  444. (parse-sexp-lookup-properties nil)
  445. (case-fold-search t))
  446. (save-excursion
  447. (save-restriction
  448. (widen)
  449. (let ((span (pm--get-intersected-span config pos)))
  450. (if (= omax pos)
  451. (when (and (= omax (nth 1 span))
  452. (> omax omin))
  453. ;; When pos == point-max and it's beg of span, return the
  454. ;; previous span. This occurs because the computation of
  455. ;; pm--get-intersected-span is done on a widened buffer.
  456. (setq span (pm--get-intersected-span config (1- pos))))
  457. (when (= pos (nth 2 span))
  458. (error "Span ends at %d in (pm--inermost-span %d) %s"
  459. pos pos (pm-format-span span))))
  460. (pm--chop-span span omin omax))))))
  461. (defun pm--cached-span (&optional pos)
  462. ;; fixme: add basic miss statistics
  463. (unless pm-initialization-in-progress
  464. (let* ((omin (point-min))
  465. (omax (point-max))
  466. (pos (or pos (point)))
  467. (pos (if (= pos omax)
  468. (max (point-min) (1- pos))
  469. pos))
  470. (span (get-text-property pos :pm-span)))
  471. (when span
  472. (save-restriction
  473. (widen)
  474. (let* ((beg (nth 1 span))
  475. (end (1- (nth 2 span))))
  476. (when (and (< end (point-max)) ; buffer size might have changed
  477. (<= pos end)
  478. (<= beg pos)
  479. (eq span (get-text-property beg :pm-span))
  480. (eq span (get-text-property end :pm-span))
  481. (not (eq span (get-text-property (1+ end) :pm-span)))
  482. (or (= beg (point-min))
  483. (not (eq span (get-text-property (1- beg) :pm-span)))))
  484. (pm--chop-span (copy-sequence span) omin omax))))))))
  485. (define-obsolete-function-alias 'pm-get-innermost-span 'pm-innermost-span "2018-08")
  486. (defun pm-innermost-span (&optional pos no-cache)
  487. "Get span object at POS.
  488. If NO-CACHE is non-nil, don't use cache and force re-computation
  489. of the span. Return a cons (type start end chunkmode). POS
  490. defaults to point. Guarantied to return a non-empty span."
  491. (when (and pos (or (< pos (point-min)) (> pos (point-max))))
  492. (signal 'args-out-of-range
  493. (list :pos pos
  494. :point-min (point-min)
  495. :point-max (point-max))))
  496. (save-match-data
  497. (or (unless no-cache
  498. (pm--cached-span pos))
  499. (pm--innermost-span pm/polymode pos))))
  500. (defun pm-span-to-range (span)
  501. (and span (cons (nth 1 span) (nth 2 span))))
  502. (define-obsolete-function-alias 'pm-get-innermost-range 'pm-innermost-range "2018-08")
  503. (defun pm-innermost-range (&optional pos no-cache)
  504. (pm-span-to-range (pm-innermost-span pos no-cache)))
  505. (defun pm-fun-matcher (matcher)
  506. "Make a function matcher given a MATCHER.
  507. MATCHER is one of the forms accepted by \=`pm-inner-chunkmode''s
  508. :head-matcher slot."
  509. (cond
  510. ((stringp matcher)
  511. (lambda (ahead)
  512. (if (< ahead 0)
  513. (if (re-search-backward matcher nil t)
  514. (cons (match-beginning 0) (match-end 0)))
  515. (if (re-search-forward matcher nil t)
  516. (cons (match-beginning 0) (match-end 0))))))
  517. ((functionp matcher)
  518. matcher)
  519. ((consp matcher)
  520. (lambda (ahead)
  521. (when (re-search-forward (car matcher) nil t ahead)
  522. (cons (match-beginning (cdr matcher))
  523. (match-end (cdr matcher))))))
  524. (t (error "Head and tail matchers must be either regexp strings, cons cells or functions"))))
  525. (defun pm-same-indent-tail-matcher (_arg)
  526. "Get the end position of block with the higher indent than the current column.
  527. Used as tail matcher for blocks identified by same indent. See
  528. function `poly-slim-mode' for examples. ARG is ignored; always search
  529. forward."
  530. ;; we are at the head end; so either use head indent or this code indent
  531. (let* ((cur-indent (current-indentation))
  532. (cur-col (current-column))
  533. (block-col (if (< cur-indent cur-col)
  534. cur-indent
  535. (1- cur-indent)))
  536. (end (point-at-eol)))
  537. (forward-line 1)
  538. (while (and (not (eobp))
  539. (or (looking-at-p "[ \t]*$")
  540. (and (> (current-indentation) block-col)
  541. (setq end (point-at-eol)))))
  542. (forward-line 1))
  543. ;; end at bol for the sake of indentation
  544. (setq end (min (point-max) (1+ end)))
  545. (cons end end)))
  546. (defun pm--get-property-nearby (property accessor ahead)
  547. (let ((ahead (> ahead 0)))
  548. (let* ((pos (if ahead
  549. (if (get-text-property (point) property)
  550. (point)
  551. (next-single-property-change (point) property))
  552. (previous-single-property-change (point) property)))
  553. (val (when pos
  554. (or (get-text-property pos property)
  555. (and (setq pos (previous-single-property-change pos property nil (point-min)))
  556. (get-text-property pos property))))))
  557. (when val
  558. (if accessor
  559. (let ((val (save-excursion
  560. (goto-char pos)
  561. (funcall accessor val))))
  562. (cond
  563. ((numberp val) (cons val val))
  564. ((consp val) (cons (car val) (if (listp (cdr val))
  565. (cadr val)
  566. (cdr val))))
  567. (t (cons pos (next-single-property-change pos property nil (point-max))))))
  568. (cons pos (next-single-property-change pos property nil (point-max))))))))
  569. (defun pm-make-text-property-matcher (property &optional accessor)
  570. "Return a head or tail matcher for PROPERTY with ACCESSOR.
  571. ACCESSOR is either a function or a keyword. When a function it is
  572. applied to the PROPERTY's value to retrieve the position of the
  573. head in the buffer. It should return either a number in which
  574. case head has 0 length, a cons of the form (BEG . END), or a
  575. list (BEG END). ACCESSOR is called at the beginning of the
  576. PROPERTY region. When ACCESSOR is nil the head span is the region
  577. covered by the same value of PROPERTY. When ACCESSOR is a keyword
  578. the property is searched as when ACCESSOR is nil but is adapted
  579. according to the keyword. Currently :inc-end means increment the
  580. END of the span, when :dec-beg, decrement the beginning of the
  581. span."
  582. (lambda (ahead)
  583. (if (keywordp accessor)
  584. (let ((loc (pm--get-property-nearby property nil ahead)))
  585. (when loc
  586. (cond
  587. ((eq accessor :inc-end) (setcdr loc (1+ (cdr loc))))
  588. ((eq accessor :dec-beg) (setcar loc (1- (cdr loc))))
  589. (t (error "Invalid ACCESSOR keyword")))
  590. loc))
  591. (pm--get-property-nearby property accessor ahead))))
  592. (defun pm--span-at-point (head-matcher tail-matcher &optional pos can-overlap do-chunk)
  593. "Span detector with head and tail matchers.
  594. HEAD-MATCHER and TAIL-MATCHER is as in :head-matcher slot of
  595. `pm-inner-chunkmode' object. POS defaults to (point). When
  596. CAN-OVERLAP is non-nil nested chunks of this type are allowed.
  597. Return a list of the form (TYPE SPAN-START SPAN-END) where TYPE
  598. is one of the following symbols:
  599. nil - pos is between point-min and head-matcher, or between
  600. tail-matcher and point-max
  601. body - pos is between head-matcher and tail-matcher (exclusively)
  602. head - head span
  603. tail - tail span
  604. Non-nil DO-CHUNK makes this function return a list of the
  605. form (TYPE HEAD-START HEAD-END TAIL-START TAIL-END)."
  606. (setq pos (or pos (point)))
  607. (save-restriction
  608. (widen)
  609. (save-excursion
  610. (goto-char pos)
  611. (let* ((at-max (= pos (point-max)))
  612. (head-matcher (pm-fun-matcher head-matcher))
  613. (tail-matcher (pm-fun-matcher tail-matcher))
  614. (head1 (funcall head-matcher -1)))
  615. (if head1
  616. (if (or (< pos (cdr head1))
  617. (and at-max (= (cdr head1) pos)))
  618. ;; -----|
  619. ;; host)[head) ; can occur with sub-head == 0 only
  620. (if do-chunk
  621. (pm--find-tail-from-head pos head1 head-matcher tail-matcher can-overlap 'head)
  622. (list 'head (car head1) (cdr head1)))
  623. ;; ------------------------
  624. ;; host)[head)[body)[tail)[host)[head)[body)
  625. (pm--find-tail-from-head pos head1 head-matcher tail-matcher can-overlap do-chunk))
  626. ;; ----------
  627. ;; host)[head)[body)[tail)[host
  628. (goto-char (point-min))
  629. (let ((head2 (funcall head-matcher 1)))
  630. (if head2
  631. (if (< pos (car head2))
  632. ;; ----
  633. ;; host)[head)[body)[tail)[host
  634. (if do-chunk
  635. (list nil (point-min) (point-min) (car head2) (car head2))
  636. (list nil (point-min) (car head2)))
  637. (if (< pos (cdr head2))
  638. ;; -----
  639. ;; host)[head)[body)[tail)[host
  640. (if do-chunk
  641. (pm--find-tail-from-head pos head2 head-matcher tail-matcher can-overlap 'head)
  642. (list 'head (car head2) (cdr head2)))
  643. ;; -----------------
  644. ;; host)[head)[body)[tail)[host
  645. (pm--find-tail-from-head pos head2 head-matcher tail-matcher can-overlap do-chunk)))
  646. ;; no span found
  647. nil)))))))
  648. ;; fixme: find a simpler way with recursion where head-matcher and tail-matcher could be reversed
  649. (defun pm--find-tail-from-head (pos head head-matcher tail-matcher can-overlap do-chunk)
  650. (goto-char (cdr head))
  651. (let ((tail (funcall tail-matcher 1))
  652. (at-max (= pos (point-max)))
  653. (type 'tail))
  654. (when can-overlap
  655. (save-excursion
  656. ;; search for next head and pick the earliest
  657. (goto-char (cdr head))
  658. (let ((match (funcall head-matcher 1)))
  659. (when (or (null tail)
  660. (and match (< (car match) (car tail))))
  661. (setq tail match
  662. type 'head)))))
  663. (if tail
  664. (if (< pos (car tail))
  665. ;; -----
  666. ;; host)[head)[body)[tail)[host)[head)
  667. (if do-chunk
  668. (list (if (eq do-chunk t) 'body do-chunk)
  669. (car head) (cdr head) (car tail) (cdr tail))
  670. (list 'body (cdr head) (car tail)))
  671. (if (or (< pos (cdr tail))
  672. (and at-max (= pos (cdr tail))))
  673. ;; -----
  674. ;; host)[head)[body)[tail)[host)[head)
  675. (if do-chunk
  676. (if (eq type 'tail)
  677. (list (if (eq do-chunk t) 'tail do-chunk)
  678. (car head) (cdr head) (car tail) (cdr tail))
  679. ;; can-overlap case
  680. (pm--find-tail-from-head pos tail head-matcher tail-matcher can-overlap do-chunk))
  681. (list type (car tail) (cdr tail)))
  682. (goto-char (cdr tail))
  683. ;; -----------
  684. ;; host)[head)[body)[tail)[host)[head)
  685. (let ((match (funcall head-matcher 1))
  686. (type 'head))
  687. (when can-overlap
  688. (save-excursion
  689. ;; search for next head and pick the earliest
  690. (goto-char (cdr tail))
  691. (let ((match2 (funcall tail-matcher 1)))
  692. (when (or (null match)
  693. (and match2 (< (car match2) (car match))))
  694. (setq match match2
  695. type 'tail)))))
  696. (if match
  697. (if (< pos (car match))
  698. ;; -----
  699. ;; host)[head)[body)[tail)[host)[head)
  700. (if do-chunk
  701. (list nil (cdr tail) (cdr tail) (car match) (car match))
  702. (list nil (cdr tail) (car match)))
  703. (if (or (< pos (cdr match))
  704. (and at-max (= pos (cdr match))))
  705. ;; -----
  706. ;; host)[head)[body)[tail)[host)[head)[body
  707. (if do-chunk
  708. (if (eq type 'tail)
  709. ;; can-overlap case
  710. (list (if (eq do-chunk t) 'tail do-chunk)
  711. (car head) (cdr head) (car match) (cdr match))
  712. (pm--find-tail-from-head pos match head-matcher tail-matcher can-overlap 'head))
  713. (list type (car match) (cdr match)))
  714. ;; ----
  715. ;; host)[head)[body)[tail)[host)[head)[body
  716. (pm--find-tail-from-head pos match head-matcher tail-matcher can-overlap do-chunk)))
  717. ;; -----
  718. ;; host)[head)[body)[tail)[host)
  719. (if do-chunk
  720. (list nil (cdr tail) (cdr tail) (point-max) (point-max))
  721. (list nil (cdr tail) (point-max)))))))
  722. ;; -----
  723. ;; host)[head)[body)
  724. (if do-chunk
  725. (list (if (eq do-chunk t) 'body do-chunk) (cdr head) (cdr head) (point-max) (point-max))
  726. (list 'body (cdr head) (point-max))))))
  727. (defun pm--next-chunk (head-matcher tail-matcher &optional pos can-overlap)
  728. "Forward only span detector.
  729. For HEAD-MATCHER, TAIL-MATCHER, POS and CAN-OVERLAP see
  730. `pm--span-at-point'. Return a list of the form (HEAD-START
  731. HEAD-END TAIL-START TAIL-END). Can return nil if there are no
  732. forward spans from pos."
  733. (setq pos (or pos (point)))
  734. (save-restriction
  735. (widen)
  736. (save-excursion
  737. (goto-char pos)
  738. (let ((parse-sexp-lookup-properties nil)
  739. (case-fold-search t)
  740. (head-matcher (pm-fun-matcher head-matcher))
  741. (tail-matcher (pm-fun-matcher tail-matcher))
  742. (head nil))
  743. ;; start from bol !! ASSUMPTION !!
  744. (forward-line 0)
  745. (setq head (funcall head-matcher 1))
  746. (while (and head (< (car head) pos))
  747. (setq head (funcall head-matcher 1)))
  748. (when head
  749. (goto-char (cdr head))
  750. (let ((tail (or (funcall tail-matcher 1)
  751. (cons (point-max) (point-max)))))
  752. (when can-overlap
  753. (goto-char (cdr head))
  754. (when-let ((hbeg (car (funcall head-matcher 1))))
  755. (when (< hbeg (car tail))
  756. (setq tail (cons hbeg hbeg)))))
  757. (list (car head) (cdr head) (car tail) (cdr tail))))))))
  758. (defun pm-goto-span-of-type (type N)
  759. "Skip to N - 1 spans of TYPE and stop at the start of a span of TYPE.
  760. TYPE is either a symbol or a list of symbols of span types."
  761. (let* ((sofar 0)
  762. (types (if (symbolp type)
  763. (list type)
  764. type))
  765. (back (< N 0))
  766. (N (if back (- N) N))
  767. (beg (if back (point-min) (point)))
  768. (end (if back (point) (point-max))))
  769. (unless (memq (car (pm-innermost-span)) types)
  770. (setq sofar 1))
  771. (condition-case nil
  772. (pm-map-over-spans
  773. (lambda (span)
  774. (when (memq (car span) types)
  775. (goto-char (nth 1 span))
  776. (when (>= sofar N)
  777. (signal 'quit nil))
  778. (setq sofar (1+ sofar))))
  779. beg end nil back)
  780. (quit nil))
  781. sofar))
  782. ;;; OBJECT HOOKS
  783. (defun pm--run-derived-mode-hooks ()
  784. ;; Minor modes run-hooks, major-modes run-mode-hooks. Polymodes is a minor
  785. ;; mode but with major-mode flavor. We run hooks of all modes stored in
  786. ;; '-minor-mode slot of all parent objects in parent-first order.
  787. (let* ((this-mode (eieio-oref pm/polymode '-minor-mode))
  788. (this-state (symbol-value this-mode)))
  789. (mapc (lambda (mm)
  790. (let ((old-state (symbol-value mm)))
  791. (unwind-protect
  792. (progn
  793. (set mm this-state)
  794. (run-hooks (derived-mode-hook-name mm)))
  795. (set mm old-state))))
  796. (pm--collect-parent-slots pm/polymode '-minor-mode))))
  797. (defun pm--run-init-hooks (object type &optional emacs-hook)
  798. (unless pm-initialization-in-progress
  799. (when emacs-hook
  800. (run-hooks emacs-hook))
  801. (pm--run-hooks object :init-functions (or type 'host))))
  802. (defun pm--collect-parent-slots (object slot &optional do-when inclusive)
  803. "Descend into parents of OBJECT and return a list of SLOT values.
  804. Returned list is in parent first order. If non-nil DO-WHEN must
  805. be a function which would take an object and return non-nil if
  806. the recursion should descend into the parent. When nil, all
  807. parents are descended. If INCLUSIVE is non-nil, include the slot
  808. of the first object for which DO-WHEN failed."
  809. (let ((inst object)
  810. (vals nil)
  811. (failed nil))
  812. (while inst
  813. (if (not (slot-boundp inst slot))
  814. (setq inst (and (slot-boundp inst :parent-instance)
  815. (eieio-oref inst 'parent-instance)))
  816. (push (eieio-oref inst slot) vals)
  817. (setq inst (and
  818. (or (null do-when)
  819. (if failed
  820. (progn (setq failed nil) t)
  821. (or (funcall do-when inst)
  822. (and inclusive
  823. (setq failed t)))))
  824. (slot-boundp inst :parent-instance)
  825. (eieio-oref inst 'parent-instance)))))
  826. vals))
  827. (defun pm--run-hooks (object slot &rest args)
  828. "Run hooks from SLOT of OBJECT and its parent instances.
  829. Parents' hooks are run first."
  830. (let ((funs (delete-dups
  831. (copy-sequence
  832. (apply #'append
  833. (pm--collect-parent-slots object slot))))))
  834. (if args
  835. (mapc (lambda (fn)
  836. (apply fn args))
  837. funs)
  838. (mapc #'funcall funs))))
  839. ;;; BUFFER SELECTION
  840. ;; Transfer of the buffer-undo-list is managed internally by emacs
  841. (define-obsolete-variable-alias 'pm-move-vars-from-base 'polymode-move-these-vars-from-base-buffer "v0.1.6")
  842. (defvar polymode-move-these-vars-from-base-buffer
  843. '(buffer-file-name
  844. ;; ideally this one should be merged across all buffers
  845. buffer-display-table
  846. outline-regexp
  847. outline-level
  848. polymode-default-inner-mode
  849. tab-width)
  850. "Variables transferred from base buffer on buffer switch.")
  851. (define-obsolete-variable-alias 'pm-move-vars-from-old-buffer 'polymode-move-these-vars-from-old-buffer "v0.1.6")
  852. (defvar polymode-move-these-vars-from-old-buffer
  853. '(buffer-face-mode
  854. buffer-face-mode-face
  855. buffer-face-mode-remapping
  856. buffer-invisibility-spec
  857. buffer-read-only
  858. buffer-undo-list
  859. buffer-undo-tree
  860. display-line-numbers
  861. face-remapping-alist
  862. isearch-mode ; this seems to be enough to avoid isearch glitching
  863. line-move-visual
  864. overwrite-mode
  865. selective-display
  866. text-scale-mode
  867. text-scale-mode-amount
  868. ;; transient-mark-mode stores here the state of selection
  869. ;; when the shift-select-mode is enabled
  870. transient-mark-mode
  871. truncate-lines
  872. truncate-partial-width-windows
  873. word-wrap
  874. ;; multiple-cursors stores here a command in a pre-command-hook
  875. ;; and executes it for all cursors in a post-command-hook so we
  876. ;; need to transfer in case the buffer was switched.
  877. mc--this-command)
  878. "Variables transferred from old buffer on buffer switch.")
  879. (defvar polymode-move-these-minor-modes-from-base-buffer nil
  880. "List of minor modes to move from base buffer.")
  881. (defvar polymode-move-these-minor-modes-from-old-buffer
  882. '(linum-mode
  883. visual-line-mode
  884. visual-fill-column-mode
  885. writeroom-mode
  886. multiple-cursors-mode)
  887. "List of minor modes to move from the old buffer.")
  888. (defun pm-own-buffer-p (&optional buffer)
  889. "Return t if BUFFER is owned by polymode.
  890. Owning a buffer means that the BUFFER is either the base buffer
  891. or an indirect implementation buffer. If nil, the buffer was
  892. created outside of polymode with `clone-indirect-buffer'."
  893. (when pm/polymode
  894. (memq (or buffer (current-buffer))
  895. (eieio-oref pm/polymode '-buffers))))
  896. (defun pm-select-buffer (span &optional visibly)
  897. "Select the buffer associated with SPAN.
  898. Install a new indirect buffer if it is not already installed.
  899. Chunkmode's class should define `pm-get-buffer-create' method. If
  900. VISIBLY is non-nil perform extra adjustment for \"visual\" buffer
  901. switch."
  902. (let ((buffer (pm-span-buffer span))
  903. (own (pm-own-buffer-p))
  904. (cbuf (current-buffer)))
  905. ;; FIXME: investigate why this one is still needed.
  906. ;; polymode-syntax-propertize should have taken care of it.
  907. (with-current-buffer buffer
  908. (pm--reset-ppss-cache span))
  909. (when (and own visibly)
  910. ;; always sync to avoid issues with tooling working in different buffers
  911. (pm--synchronize-points cbuf)
  912. (let ((mode (or (eieio-oref (nth 3 span) 'keep-in-mode)
  913. (eieio-oref pm/polymode 'keep-in-mode))))
  914. (setq buffer (cond
  915. ((null mode) buffer)
  916. ((eq mode 'host) (pm-base-buffer))
  917. (mode (or (pm-get-buffer-of-mode mode)
  918. ;; not throwing because in auto-modes mode might not
  919. ;; be installed yet and there is no way install it
  920. ;; from here
  921. buffer))))))
  922. ;; (message "setting buffer %d-%d [%s]" (nth 1 span) (nth 2 span) cbuf)
  923. ;; no further action if BUFFER is already the current buffer
  924. (unless (eq buffer cbuf)
  925. (when (and own visibly)
  926. (run-hook-with-args 'polymode-before-switch-buffer-hook
  927. cbuf buffer))
  928. (pm--move-vars polymode-move-these-vars-from-base-buffer
  929. (pm-base-buffer) buffer)
  930. (pm--move-vars polymode-move-these-vars-from-old-buffer
  931. cbuf buffer)
  932. (if visibly
  933. ;; Slow, visual selection. Don't perform in foreign indirect buffers.
  934. (when own
  935. (pm--select-existing-buffer-visibly buffer))
  936. (set-buffer buffer)))))
  937. (defvar text-scale-mode)
  938. (defvar text-scale-mode-amount)
  939. (defun pm--select-existing-buffer-visibly (new-buffer)
  940. (let ((old-buffer (current-buffer))
  941. (point (point))
  942. (window-start (window-start))
  943. (visible (pos-visible-in-window-p))
  944. (ractive (region-active-p))
  945. (mkt (mark t)))
  946. (when pm-hide-implementation-buffers
  947. (rename-buffer (pm--hidden-buffer-name)))
  948. (setq pm/current nil)
  949. (pm--move-minor-modes polymode-move-these-minor-modes-from-base-buffer
  950. (pm-base-buffer) new-buffer)
  951. (pm--move-minor-modes polymode-move-these-minor-modes-from-old-buffer
  952. old-buffer new-buffer)
  953. (pm--move-overlays old-buffer new-buffer)
  954. (switch-to-buffer new-buffer)
  955. (bury-buffer-internal old-buffer)
  956. (set-window-prev-buffers nil (assq-delete-all old-buffer (window-prev-buffers nil)))
  957. (setq pm/current t)
  958. ;; fixme: what is the right way to do this ... activate-mark-hook?
  959. (if (not ractive)
  960. (deactivate-mark)
  961. (set-mark mkt)
  962. (activate-mark))
  963. (when pm-hide-implementation-buffers
  964. (rename-buffer (pm--visible-buffer-name)))
  965. ;; avoid display jumps
  966. (goto-char point)
  967. (when visible
  968. (set-window-start (get-buffer-window new-buffer t) window-start))
  969. (run-hook-with-args 'polymode-after-switch-buffer-hook old-buffer new-buffer)
  970. (pm--run-hooks pm/polymode :switch-buffer-functions old-buffer new-buffer)
  971. (pm--run-hooks pm/chunkmode :switch-buffer-functions old-buffer new-buffer)))
  972. (defun pm--move-overlays (from-buffer to-buffer)
  973. (with-current-buffer from-buffer
  974. (mapc (lambda (o)
  975. (unless (or (overlay-get o 'linum-str)
  976. (overlay-get o 'yas--snippet))
  977. (move-overlay o (overlay-start o) (overlay-end o) to-buffer)))
  978. (overlays-in 1 (1+ (buffer-size))))))
  979. (defun pm--move-vars (vars from-buffer &optional to-buffer)
  980. (let ((to-buffer (or to-buffer (current-buffer))))
  981. (unless (eq to-buffer from-buffer)
  982. (with-current-buffer to-buffer
  983. (dolist (var vars)
  984. (when (default-boundp var)
  985. (make-local-variable var)
  986. (set var (buffer-local-value var from-buffer))))))))
  987. (defun pm--move-minor-modes (modes from-buffer &optional to-buffer)
  988. (let ((to-buffer (or to-buffer (current-buffer))))
  989. (unless (eq to-buffer from-buffer)
  990. (with-current-buffer to-buffer
  991. (dolist (m modes)
  992. (when (default-boundp m)
  993. (let ((from-state (buffer-local-value m from-buffer)))
  994. (unless (equal from-state (symbol-value m))
  995. (funcall (symbol-function m) (if from-state 1 -1))))))))))
  996. (defun pm-set-buffer (&optional pos-or-span)
  997. "Set buffer to polymode buffer appropriate for POS-OR-SPAN.
  998. This is done with `set-buffer' and no visual adjustments (like
  999. overlay transport) are done. See `pm-switch-to-buffer' for a more
  1000. comprehensive alternative."
  1001. (let ((span (if (or (null pos-or-span)
  1002. (number-or-marker-p pos-or-span))
  1003. (pm-innermost-span pos-or-span)
  1004. pos-or-span)))
  1005. (pm-select-buffer span)))
  1006. ;; NB: Polymode functions used in emacs utilities should not switch buffers
  1007. ;; under any circumstances. Switching should happen only in post-command. For
  1008. ;; example `pm-indent-line-dispatcher' used to switch buffers, but it was called
  1009. ;; from electric-indent-post-self-insert-function in post-self-insert-hook which
  1010. ;; was triggered by self-insert-command called from `newline'. `newline' sets a
  1011. ;; temporary local post-self-insert-hook and makes the assumption that buffer
  1012. ;; stays the same. It was moved away from original buffer by polymode's
  1013. ;; indentation dispatcher its post-self-insert-hook hanged permanently in the
  1014. ;; old buffer (#226).
  1015. (defun pm-switch-to-buffer (&optional pos-or-span)
  1016. "Bring the appropriate polymode buffer to front.
  1017. POS-OR-SPAN can be either a position in a buffer or a span. All
  1018. expensive adjustment for a visible switch (like overlay
  1019. transport) are performed."
  1020. (let ((span (if (or (null pos-or-span)
  1021. (number-or-marker-p pos-or-span))
  1022. (pm-innermost-span pos-or-span)
  1023. pos-or-span)))
  1024. (pm-select-buffer span 'visibly)))
  1025. (defun pm-map-over-modes (fn beg end)
  1026. (when (< beg end)
  1027. (save-restriction
  1028. (widen)
  1029. (let* ((hostmode (eieio-oref pm/polymode '-hostmode))
  1030. (pos beg)
  1031. (ttype 'dummy)
  1032. span nspan nttype)
  1033. (when (< (point-min) beg)
  1034. (setq span (pm-innermost-span beg)
  1035. beg (nth 1 span)
  1036. pos (nth 2 span)
  1037. ttype (pm-true-span-type span))
  1038. (while (and (memq (car span) '(head body))
  1039. (< pos end))
  1040. (setq nspan (pm-innermost-span (nth 2 span))
  1041. nttype (pm-true-span-type nspan))
  1042. (if (eq ttype nttype)
  1043. (setq pos (nth 2 nspan))
  1044. (with-current-buffer (pm-span-buffer span)
  1045. (funcall fn beg pos))
  1046. (setq beg (nth 1 nspan)
  1047. pos (nth 2 nspan)))
  1048. (setq span nspan
  1049. ttype nttype)))
  1050. (when (< pos end)
  1051. (let ((ichunks (cl-loop for im in (eieio-oref pm/polymode '-innermodes)
  1052. collect (cons im nil)))
  1053. (tichunks nil)
  1054. (spans nil))
  1055. (while (< pos end)
  1056. ;; 1. recompute outdated chunks
  1057. (setq tichunks nil)
  1058. (dolist (ichunk ichunks)
  1059. (if (and (cdr ichunk)
  1060. (< pos (nth 5 ichunk)))
  1061. (push ichunk tichunks)
  1062. (let ((nchunk (pm-next-chunk (car ichunk) pos)))
  1063. (when nchunk
  1064. (push (cons (car ichunk) nchunk) tichunks)))))
  1065. (setq ichunks (reverse tichunks))
  1066. ;; 2. Compute all (next) spans
  1067. (setq spans nil)
  1068. (dolist (ichunk ichunks)
  1069. (let ((chunk (cdr ichunk)))
  1070. (let ((span (cond
  1071. ((< pos (nth 1 chunk)) (list nil pos (nth 1 chunk) (car chunk)))
  1072. ((< pos (nth 2 chunk)) (list 'head (nth 1 chunk) (nth 2 chunk) (car chunk)))
  1073. ((< pos (nth 3 chunk)) (list 'body (nth 2 chunk) (nth 3 chunk) (car chunk)))
  1074. ((< pos (nth 4 chunk)) (list 'tail (nth 3 chunk) (nth 4 chunk) (car chunk))))))
  1075. (push span spans))))
  1076. (setq spans (nreverse spans))
  1077. ;; 3. Intersect
  1078. (setq nspan (list nil pos (point-max) hostmode))
  1079. (dolist (s spans)
  1080. (setq nspan (pm--intersect-spans nspan s)))
  1081. ;; (setq pm--span-counter (1+ pm--span-counter)) ;; for debugging
  1082. (pm-cache-span nspan)
  1083. (setq nttype (pm-true-span-type nspan))
  1084. ;; 4. funcall on region if type changed
  1085. (unless (eq ttype nttype)
  1086. (when span
  1087. (with-current-buffer (pm-span-buffer span)
  1088. (funcall fn beg pos)))
  1089. (setq ttype nttype
  1090. beg (nth 1 nspan)))
  1091. (setq span nspan
  1092. pos (nth 2 nspan)))))
  1093. (with-current-buffer (pm-span-buffer span)
  1094. (funcall fn beg pos))))))
  1095. ;; ;; do not delete: speed and consistency checks
  1096. ;; (defvar pm--span-counter 0)
  1097. ;; (defvar pm--mode-counter 0)
  1098. ;; (defun pm-debug-map-over-modes-test (&optional beg end)
  1099. ;; (interactive)
  1100. ;; (setq pm--span-counter 0)
  1101. ;; (setq pm--mode-counter 0)
  1102. ;; (pm-map-over-modes
  1103. ;; (lambda (beg end)
  1104. ;; (setq pm--mode-counter (1+ pm--mode-counter)))
  1105. ;; (or beg (point-min))
  1106. ;; (or end (point-max)))
  1107. ;; (cons pm--span-counter pm--mode-counter))
  1108. ;; (defun pm-debug-map-over-spans-test (&optional beg end)
  1109. ;; (interactive)
  1110. ;; (setq pm--span-counter 0)
  1111. ;; (pm-map-over-spans
  1112. ;; (lambda (span)
  1113. ;; (setq pm--span-counter (1+ pm--span-counter)))
  1114. ;; (or beg (point-min))
  1115. ;; (or end (point-max)))
  1116. ;; pm--span-counter)
  1117. (defun pm-map-over-spans (fun &optional beg end count backwardp visibly no-cache)
  1118. "For all spans between BEG and END, execute FUN.
  1119. FUN is a function of one argument a span object (also available
  1120. in a dynamic variable *span*). Buffer is *not* narrowed to the
  1121. span, nor point is moved. If COUNT is non-nil, jump at most that
  1122. many times. If BACKWARDP is non-nil, map backwards. Point
  1123. synchronization across indirect buffers is not taken care of.
  1124. Modification of the buffer during mapping is an undefined
  1125. behavior."
  1126. ;; Important! Don't forget to save-excursion when calling map-overs-spans and
  1127. ;; synchronize points if needed. Mapping can end in different buffer and
  1128. ;; invalidate the caller assumptions.
  1129. (save-restriction
  1130. (widen)
  1131. (setq beg (or beg (point-min))
  1132. end (if end
  1133. (min end (point-max))
  1134. (point-max)))
  1135. (unless count
  1136. (setq count most-positive-fixnum))
  1137. (let* ((nr 0)
  1138. (pos (if backwardp end beg))
  1139. (*span* (pm-innermost-span pos no-cache)))
  1140. (while *span*
  1141. (setq nr (1+ nr))
  1142. (pm-select-buffer *span* visibly)
  1143. ;; FUN might change buffer and invalidate our *span*. Should we care or
  1144. ;; reserve pm-map-over-spans for "read-only" actions only? Does
  1145. ;; after-change run immediately or after this function ends?
  1146. (funcall fun *span*)
  1147. ;; enter previous/next chunk
  1148. (setq pos
  1149. (if backwardp
  1150. (max 1 (1- (nth 1 *span*)))
  1151. (min (point-max) (nth 2 *span*))))
  1152. (setq *span*
  1153. (and (if backwardp
  1154. (> pos beg)
  1155. (< pos end))
  1156. (< nr count)
  1157. (pm-innermost-span pos no-cache)))))))
  1158. (defun pm-narrow-to-span (&optional span)
  1159. "Narrow to current SPAN."
  1160. (interactive)
  1161. (unless (= (point-min) (point-max))
  1162. (let ((span (or span
  1163. (pm-innermost-span))))
  1164. (let ((sbeg (nth 1 span))
  1165. (send (nth 2 span)))
  1166. (unless pm--emacs>26
  1167. (pm--reset-ppss-cache span))
  1168. (narrow-to-region sbeg send)))))
  1169. (defmacro pm-with-narrowed-to-span (span &rest body)
  1170. (declare (indent 1) (debug body))
  1171. `(save-restriction
  1172. (pm-narrow-to-span ,span)
  1173. ,@body))
  1174. ;;; HOOKS
  1175. ;; There is also `poly-lock-after-change' in poly-lock.el
  1176. (defun polymode-flush-syntax-ppss-cache (beg end _)
  1177. "Run `syntax-ppss-flush-cache' from BEG to END in all polymode buffers."
  1178. ;; Modification hooks are run only in current buffer and not in other (base or
  1179. ;; indirect) buffers. Thus some actions like flush of ppss cache must be taken
  1180. ;; care explicitly. We run some safety hooks checks here as well.
  1181. (dolist (buff (oref pm/polymode -buffers))
  1182. (when (buffer-live-p buff)
  1183. (with-current-buffer buff
  1184. ;; micro-optimization to avoid calling the flush twice
  1185. (when (memq 'syntax-ppss-flush-cache before-change-functions)
  1186. (remove-hook 'before-change-functions 'syntax-ppss-flush-cache t))
  1187. ;; need to be the first to avoid breaking preceding hooks
  1188. (unless (eq (car after-change-functions)
  1189. 'polymode-flush-syntax-ppss-cache)
  1190. (delq 'polymode-flush-syntax-ppss-cache after-change-functions)
  1191. (add-hook 'after-change-functions 'polymode-flush-syntax-ppss-cache nil t))
  1192. (syntax-ppss-flush-cache beg end)
  1193. ;; Check if something has changed our hooks. (Am I theoretically paranoid or
  1194. ;; this is indeed needed?) `fontification-functions' (and others?) should be
  1195. ;; checked as well I guess.
  1196. ;; (when (memq 'font-lock-after-change-function after-change-functions)
  1197. ;; (remove-hook 'after-change-functions 'font-lock-after-change-function t))
  1198. ;; (when (memq 'jit-lock-after-change after-change-functions)
  1199. ;; (remove-hook 'after-change-functions 'jit-lock-after-change t))
  1200. ))))
  1201. (defun polymode-pre-command-synchronize-state ()
  1202. "Synchronize state between buffers.
  1203. Currently synchronize points only. Runs in local `pre-command-hook'."
  1204. (pm--synchronize-points (current-buffer)))
  1205. (defun polymode-post-command-select-buffer ()
  1206. "Select the appropriate (indirect) buffer corresponding to point's context.
  1207. This funciton is placed in local `post-command-hook'."
  1208. (when (and pm-allow-post-command-hook
  1209. polymode-mode
  1210. pm/chunkmode)
  1211. (condition-case err
  1212. (pm-switch-to-buffer)
  1213. (error (message "(pm-switch-to-buffer %s): %s"
  1214. (point) (error-message-string err))))))
  1215. (defvar-local pm--killed nil)
  1216. (defun polymode-after-kill-fixes ()
  1217. "Various fixes for polymode indirect buffers."
  1218. (when (pm-own-buffer-p)
  1219. (let ((base (pm-base-buffer)))
  1220. (set-buffer-modified-p nil)
  1221. ;; Prevent various tools like `find-file' to re-find this file.
  1222. ;;
  1223. ;; We use buffer-list instead of `-buffers' slot here because on some
  1224. ;; occasions there are other indirect buffers (e.g. switch from polymode
  1225. ;; to other mode and then back, or when user or a tool (e.g. org-capture)
  1226. ;; creates an indirect buffer manually).
  1227. (dolist (b (buffer-list))
  1228. (when (and (buffer-live-p b)
  1229. (eq (buffer-base-buffer b) base))
  1230. (with-current-buffer b
  1231. (setq pm--killed t)
  1232. (setq buffer-file-name nil)
  1233. (setq buffer-file-number nil)
  1234. (setq buffer-file-truename nil)))))))
  1235. (defun pm-turn-polymode-off (&optional new-mode)
  1236. "Remove all polymode indirect buffers and install NEW-MODE if any.
  1237. NEW-MODE can be t in which case mode is picked from the
  1238. `pm/polymode' object."
  1239. (when pm/polymode
  1240. (let* ((base (pm-base-buffer))
  1241. (mmode (buffer-local-value 'major-mode base))
  1242. (kill-buffer-hook (delete 'polymode-after-kill-fixes (copy-sequence kill-buffer-hook))))
  1243. ;; remove only our own indirect buffers
  1244. (dolist (b (eieio-oref pm/polymode '-buffers))
  1245. (unless (eq b base)
  1246. (kill-buffer b)))
  1247. (with-current-buffer base
  1248. (setq pm/polymode nil)
  1249. (when new-mode
  1250. (if (eq new-mode t)
  1251. (funcall mmode)
  1252. (funcall new-mode)))))))
  1253. (defun polymode-after-change-major-mode-cleanup ()
  1254. "Remove all polymode implementation buffers on mode change."
  1255. ;; pm/polymode is permanent local. Nil polymode-mode means that the user
  1256. ;; called another mode on top of polymode.
  1257. (when (and pm/polymode (not polymode-mode))
  1258. ;; if another mode was called from an innermode, it was installed in a wrong place
  1259. (let* ((base (pm-base-buffer))
  1260. (mmode (unless (eq base (current-buffer))
  1261. major-mode)))
  1262. (unless (eq base (current-buffer))
  1263. (when (eq (window-buffer) (current-buffer))
  1264. (switch-to-buffer base)))
  1265. (pm-turn-polymode-off mmode))))
  1266. (add-hook 'after-change-major-mode-hook #'polymode-after-change-major-mode-cleanup)
  1267. ;;; CORE ADVICE
  1268. (defun pm-around-advice (fun advice)
  1269. "Apply around ADVICE to FUN.
  1270. If FUN is a list, apply ADVICE to each element of it."
  1271. (cond ((listp fun)
  1272. (dolist (el fun) (pm-around-advice el advice)))
  1273. ((and (symbolp fun)
  1274. (not (advice-member-p advice fun)))
  1275. (advice-add fun :around advice))))
  1276. (defun polymode-inhibit-during-initialization (orig-fun &rest args)
  1277. "Don't run ORIG-FUN (with ARGS) during polymode setup."
  1278. (unless pm-initialization-in-progress
  1279. (apply orig-fun args)))
  1280. (defun polymode-with-current-base-buffer (orig-fun &rest args)
  1281. "Switch to base buffer and apply ORIG-FUN to ARGS.
  1282. Used in advises."
  1283. (if (and polymode-mode
  1284. (not pm--killed)
  1285. (buffer-live-p (buffer-base-buffer)))
  1286. (let (;; (pm-initialization-in-progress t) ; just in case
  1287. (cur-buf (current-buffer))
  1288. (base (buffer-base-buffer))
  1289. (first-arg (car-safe args)))
  1290. (prog1 (with-current-buffer base
  1291. (if (or (eq first-arg cur-buf)
  1292. (equal first-arg (buffer-name cur-buf)))
  1293. (apply orig-fun base (cdr args))
  1294. (apply orig-fun args)))
  1295. ;; The sync of points doesn't work as expected in the following corner
  1296. ;; case: if current buffer is an indirect one and a function operates
  1297. ;; on the base buffer (like save-buffer) and somehow inadvertently
  1298. ;; moves points in the indirect buffer then we synchronize wrong point
  1299. ;; (from the current indirect buffer). For unclear reasons the very
  1300. ;; low level scan-lists moves points in indirect buffers (FIXME: EMACS
  1301. ;; bug, report ASAP). Unfortunately save-excursion protects only from
  1302. ;; point moves in the current buffer.
  1303. (when pm/polymode
  1304. (pm--synchronize-points base))))
  1305. (apply orig-fun args)))
  1306. ;; (pm-around-advice #'kill-buffer #'polymode-with-current-base-buffer)
  1307. (pm-around-advice #'find-alternate-file #'polymode-with-current-base-buffer)
  1308. (pm-around-advice #'write-file #'polymode-with-current-base-buffer)
  1309. (pm-around-advice #'basic-save-buffer #'polymode-with-current-base-buffer)
  1310. ;; (advice-remove #'kill-buffer #'polymode-with-current-base-buffer)
  1311. ;; (advice-remove #'find-alternate-file #'polymode-with-current-base-buffer)
  1312. ;;; FILL
  1313. ;; FIXME: this is an incomplete heuristic and breaks on adjacent multi-span
  1314. ;; fill-region depending on the mode's fill-forward-paragraph-function. For a
  1315. ;; complete solution one might likely need to define fill-paragraph-function as
  1316. ;; well.
  1317. (defun polymode-fill-forward-paragraph (&optional arg)
  1318. "Function for `fill-forward-paragraph-function'.
  1319. ARG is the same as in `forward-paragraph'"
  1320. (let* ((neg (< arg 0))
  1321. (cur-span (pm-innermost-span (if neg (1- (point)) (point))))
  1322. (cur-mode (pm-span-mode cur-span))
  1323. (out (funcall (or pm--fill-forward-paragraph-original
  1324. #'forward-paragraph)
  1325. arg))
  1326. (new-mode (pm-span-mode (pm-innermost-span (point)))))
  1327. (unless (eq cur-mode new-mode)
  1328. ;; adjust to the most recent span border and hope for the best
  1329. (pm-goto-span-of-type (car cur-span) (if neg 1 -1)))
  1330. out))
  1331. ;;; SYNTAX
  1332. (defun pm--call-syntax-propertize-original (start end)
  1333. (condition-case err
  1334. (funcall pm--syntax-propertize-function-original start end)
  1335. (error
  1336. (message "ERROR: (%s %d %d) -> %s"
  1337. (if (symbolp pm--syntax-propertize-function-original)
  1338. pm--syntax-propertize-function-original
  1339. (format "polymode-syntax-propertize:%s" major-mode))
  1340. start end
  1341. ;; (backtrace)
  1342. (error-message-string err)))))
  1343. (defun polymode-syntax-propertize-extend-region-in-host (start end)
  1344. (let ((base (pm-base-buffer))
  1345. (min (point-min))
  1346. (max (point-max)))
  1347. (when base
  1348. (with-current-buffer base
  1349. (save-restriction
  1350. (narrow-to-region min max)
  1351. ;; Relevant part from syntax-propertize
  1352. (let ((funs syntax-propertize-extend-region-functions)
  1353. (extended nil))
  1354. (while funs
  1355. (let* ((syntax-propertize--done most-positive-fixnum)
  1356. (fn (pop funs))
  1357. (new (unless (eq fn 'syntax-propertize-wholelines)
  1358. (funcall fn start end))))
  1359. (when (and new
  1360. (or (< (car new) start)
  1361. (> (cdr new) end)))
  1362. (setq extended t
  1363. start (car new)
  1364. end (cdr new))
  1365. ;; If there's been a change, we should go through the list again
  1366. ;; since this new position may warrant a different answer from
  1367. ;; one of the funs we've already seen.
  1368. (unless (eq funs (cdr syntax-propertize-extend-region-functions))
  1369. (setq funs syntax-propertize-extend-region-functions)))))
  1370. (when extended (cons start end))))))))
  1371. ;; used for hard debugging of syntax properties in batch mode
  1372. (defun pm--syntax-after (pos)
  1373. (let ((syntax (syntax-after pos)))
  1374. (with-temp-buffer
  1375. (internal-describe-syntax-value syntax)
  1376. (buffer-string))))
  1377. ;; called from syntax-propertize and thus at the beginning of syntax-ppss
  1378. (defun polymode-syntax-propertize (beg end)
  1379. ;; (message "SP:%d-%d" beg end)
  1380. (unless pm-initialization-in-progress
  1381. (save-restriction
  1382. (widen)
  1383. (save-excursion
  1384. ;; some modes don't save data in their syntax propertize functions
  1385. (save-match-data
  1386. (let ((base (pm-base-buffer))
  1387. (protect-host (with-current-buffer (pm-base-buffer)
  1388. (eieio-oref pm/chunkmode 'protect-syntax))))
  1389. ;; 1. host if no protection
  1390. (unless protect-host
  1391. (with-current-buffer base
  1392. (set 'syntax-propertize--done end)
  1393. ;; (message "sp:%s:%d-%d" major-mode beg end)
  1394. (when pm--syntax-propertize-function-original
  1395. ;; For syntax matchers the host mode syntax prioritization
  1396. ;; should be smart enough to install relevant elements around
  1397. ;; end for the followup map-over-modes to work correctly.
  1398. (pm--call-syntax-propertize-original beg end))))
  1399. ;; 2. all others
  1400. (let ((last-ppss nil))
  1401. (pm-map-over-modes
  1402. (lambda (mbeg mend)
  1403. ;; Cannot set this earlier because some buffers might not be
  1404. ;; created when this function is called. One major reason to
  1405. ;; set this here is to avoid recurring into syntax-propertize
  1406. ;; when propertize functions call syntax-ppss. `setq' doesn't
  1407. ;; have an effect because the var is let bound but `set'
  1408. ;; works.
  1409. (set 'syntax-propertize--done (max end mend))
  1410. ;; (message "sp:%s:%d-%d" major-mode (max beg mbeg) mend)
  1411. (if (eq base (current-buffer))
  1412. (when protect-host
  1413. (pm--reset-ppss-cache-0 mbeg last-ppss)
  1414. (when pm--syntax-propertize-function-original
  1415. (pm--call-syntax-propertize-original (max beg mbeg) mend))
  1416. (setq last-ppss (syntax-ppss mend)))
  1417. (pm--reset-ppss-cache-0 mbeg)
  1418. (when pm--syntax-propertize-function-original
  1419. (pm--call-syntax-propertize-original (max beg mbeg) mend))))
  1420. beg end))))))))
  1421. (defvar syntax-ppss-wide)
  1422. (defvar syntax-ppss-last)
  1423. (defvar syntax-ppss-cache)
  1424. (defun pm--reset-ppss-cache (span)
  1425. "Reset `syntax-ppss-last' cache if it was recorded before SPAN's start."
  1426. (let ((sbeg (nth 1 span))
  1427. new-ppss)
  1428. (unless (car span)
  1429. ;; Host chunk is special. Pick ppss from end of last span. Body chunks
  1430. ;; with nested inner chunks should be treated the same but no practical
  1431. ;; example showed so far.
  1432. (save-restriction
  1433. (widen)
  1434. (save-excursion
  1435. (let ((pos sbeg))
  1436. (while (and (null new-ppss)
  1437. (not (= pos (point-min))))
  1438. (let ((prev-span (pm-innermost-span (1- pos))))
  1439. (if (null (car prev-span))
  1440. (setq new-ppss (syntax-ppss pos))
  1441. (setq pos (nth 1 prev-span)))))))))
  1442. (pm--reset-ppss-cache-0 sbeg new-ppss)))
  1443. (defun pm--reset-ppss-cache-0 (pos &optional new-ppss)
  1444. (unless new-ppss
  1445. (setq new-ppss (list 0 nil pos nil nil nil 0 nil nil nil nil)))
  1446. ;; in emacs 26 there are two caches syntax-ppss-wide and
  1447. ;; syntax-ppss-narrow. The latter is reset automatically each time a
  1448. ;; different narrowing is in place so we don't deal with it for now.
  1449. (let ((cache (if pm--emacs>26
  1450. (cdr syntax-ppss-wide)
  1451. syntax-ppss-cache)))
  1452. (while (and cache (>= (caar cache) pos))
  1453. (setq cache (cdr cache)))
  1454. (setq cache (cons (cons pos new-ppss) cache))
  1455. (if pm--emacs>26
  1456. ;; syntax-ppss involves an aggressive cache cleaning; protect for one
  1457. ;; such cleaning by double entry
  1458. (setq syntax-ppss-wide (cons (car cache) cache))
  1459. (setq syntax-ppss-cache cache)
  1460. (setq syntax-ppss-last (cons pos new-ppss))))
  1461. new-ppss)
  1462. ;; (defun polymode-reset-ppss-cache (&optional pos)
  1463. ;; "Reset `syntax-ppss' cache to POS in polymode buffers.
  1464. ;; Used in :before advice of `syntax-ppss'."
  1465. ;; (when polymode-mode
  1466. ;; (pm--reset-ppss-cache (pm-innermost-span pos))))
  1467. ;; (advice-add #'syntax-ppss :before #'polymode-reset-ppss-cache)
  1468. ;; (unless pm--emacs>26
  1469. ;; (advice-add #'syntax-ppss :before #'polymode-reset-ppss-cache))
  1470. ;; (defun polymode-restrict-syntax-propertize-extension (orig-fun beg end)
  1471. ;; (if (and polymode-mode pm/polymode)
  1472. ;; (let ((span (pm-innermost-span beg)))
  1473. ;; (if (eieio-oref (nth 3 span) 'protect-syntax)
  1474. ;; (let ((range (pm-span-to-range span)))
  1475. ;; (if (and (eq beg (car range))
  1476. ;; (eq end (cdr range)))
  1477. ;; ;; in the most common case when span == beg-end, simply return
  1478. ;; range
  1479. ;; (let ((be (funcall orig-fun beg end)))
  1480. ;; (and be
  1481. ;; (cons (max (car be) (car range))
  1482. ;; (min (cdr be) (cdr range)))))))
  1483. ;; (funcall orig-fun beg end)))
  1484. ;; (funcall orig-fun beg end)))
  1485. ;;; INTERNAL UTILITIES
  1486. (defun pm--set-transient-map (commands)
  1487. "Set transient map with COMMANDS.
  1488. COMMANDS is a list of commands which are bound to their
  1489. accessible keys as well as the basic event of those keys. Used
  1490. for \"cycling\" commands."
  1491. (let ((map (make-sparse-keymap)))
  1492. (mapc (lambda (cmd)
  1493. (mapc (lambda (vec)
  1494. (define-key map vec cmd)
  1495. (let ((basic-ev (elt vec (1- (length vec)))))
  1496. (define-key map (vector basic-ev) cmd)))
  1497. (where-is-internal cmd)))
  1498. commands)
  1499. (set-transient-map map)))
  1500. (defun pm--display-file (ofile)
  1501. (when ofile
  1502. ;; errors might occur (most notably with open-with package errors are intentional)
  1503. ;; We need to catch those if we want to display multiple files like with Rmarkdown
  1504. (condition-case-unless-debug err
  1505. (let ((buff (get-file-buffer ofile)))
  1506. (when buff
  1507. (with-current-buffer buff
  1508. (with-demoted-errors "Error while reverting: %s"
  1509. ;; FIXME: something is not right with pdflatex export with
  1510. ;; pdf-tools viewer within emacs
  1511. (revert-buffer t t))))
  1512. (when (if (functionp polymode-display-output-file)
  1513. (funcall polymode-display-output-file ofile)
  1514. polymode-display-output-file)
  1515. (if (string-match-p "html\\|htm$" ofile)
  1516. (browse-url ofile)
  1517. (display-buffer (find-file-noselect ofile 'nowarn)))))
  1518. (error (message "Error while displaying '%s': %s"
  1519. (file-name-nondirectory ofile)
  1520. (error-message-string err))))))
  1521. (defun pm--symbol-name (str-or-symbol)
  1522. (if (symbolp str-or-symbol)
  1523. (symbol-name str-or-symbol)
  1524. str-or-symbol))
  1525. (defun pm--true-mode-symbol (mode)
  1526. "Resolve aliases of MODE and return the true MODE name."
  1527. (while (and mode (symbolp (symbol-function mode)))
  1528. (setq mode (symbol-function mode)))
  1529. mode)
  1530. (defun pm--get-existing-mode (mode fallback)
  1531. "Return MODE symbol if it's defined and is a valid function.
  1532. If so, return it, otherwise check in turn
  1533. `polymode-default-inner-mode', the FALLBACK and ultimately
  1534. `poly-fallback-mode'."
  1535. (pm--true-mode-symbol
  1536. (cond ((fboundp mode) mode)
  1537. ((eq polymode-default-inner-mode 'host) (buffer-local-value 'major-mode (pm-base-buffer)))
  1538. ((fboundp polymode-default-inner-mode) polymode-default-inner-mode)
  1539. ((eq fallback 'host) (buffer-local-value 'major-mode (pm-base-buffer)))
  1540. ((fboundp fallback) fallback)
  1541. (t 'poly-fallback-mode))))
  1542. (defun pm--get-innermode-mode (chunkmode type)
  1543. "Retrieve the mode name of for inner CHUNKMODE for span of TYPE."
  1544. (pm--get-existing-mode
  1545. (cl-case (pm-true-span-type chunkmode type)
  1546. (body (eieio-oref chunkmode 'mode))
  1547. (head (eieio-oref chunkmode 'head-mode))
  1548. (tail (eieio-oref chunkmode 'tail-mode))
  1549. (t (error "Invalid type (%s); must be one of body, head tail" type)))
  1550. (eieio-oref chunkmode 'fallback-mode)))
  1551. ;; Used in auto innermode detection only and can return symbol 'host as that's
  1552. ;; needed in pm--get-auto-chunkmode.
  1553. (defun pm-get-mode-symbol-from-name (name &optional fallback)
  1554. "Guess and return mode function from short NAME.
  1555. Return FALLBACK if non-nil, otherwise the value of
  1556. `polymode-default-inner-mode' if non-nil, otherwise value of slot
  1557. :fallback-mode which globally defaults to `poly-fallback-mode'."
  1558. (pm--true-mode-symbol
  1559. (cond
  1560. ;; anonymous chunk
  1561. ((or (null name)
  1562. (and (stringp name) (= (length name) 0)))
  1563. (or
  1564. (when (or (eq polymode-default-inner-mode 'host)
  1565. (fboundp polymode-default-inner-mode))
  1566. polymode-default-inner-mode)
  1567. (when (or (eq fallback 'host)
  1568. (fboundp fallback))
  1569. fallback)
  1570. 'poly-fallback-mode))
  1571. ;; proper mode symbol
  1572. ((and (symbolp name) (fboundp name) name))
  1573. ;; compute from name
  1574. ((let* ((str (pm--symbol-name
  1575. (or (cdr (assq (intern (pm--symbol-name name))
  1576. polymode-mode-name-aliases))
  1577. name)))
  1578. (mname (if (string-match-p "-mode$" str)
  1579. str
  1580. (concat str "-mode"))))
  1581. (or
  1582. ;; direct search
  1583. (let ((mode (intern mname)))
  1584. (when (fboundp mode)
  1585. mode))
  1586. ;; downcase
  1587. (let ((mode (intern (downcase mname))))
  1588. (when (fboundp mode)
  1589. mode))
  1590. ;; auto-mode alist
  1591. (let ((dummy-file (concat "a." str)))
  1592. (cl-loop for (k . v) in auto-mode-alist
  1593. if (and (string-match-p k dummy-file)
  1594. (not (string-match-p "^poly-" (symbol-name v))))
  1595. return v))
  1596. (when (or (eq polymode-default-inner-mode 'host)
  1597. (fboundp polymode-default-inner-mode))
  1598. polymode-default-inner-mode)
  1599. (when (or (eq fallback 'host)
  1600. (fboundp fallback))
  1601. fallback)
  1602. 'poly-fallback-mode))))))
  1603. (defun pm--oref-with-parents (object slot)
  1604. "Merge slots SLOT from the OBJECT and all its parent instances."
  1605. (let (VALS)
  1606. (while object
  1607. (setq VALS (append (and (slot-boundp object slot) ; don't cascade
  1608. (eieio-oref object slot))
  1609. VALS)
  1610. object (and (slot-boundp object :parent-instance)
  1611. (eieio-oref object 'parent-instance))))
  1612. VALS))
  1613. (defun pm--abrev-names (abrev-regexp list)
  1614. "Abbreviate names in LIST by erasing ABREV-REGEXP matches.
  1615. Elements of LIST can be either strings or symbols."
  1616. (mapcar (lambda (nm)
  1617. (let* ((str-nm (if (symbolp nm)
  1618. (symbol-name nm)
  1619. nm))
  1620. (prefix (replace-regexp-in-string "^poly-[^-]+\\(.+\\)" "" str-nm nil nil 1))
  1621. (is-lib (or (string= prefix "poly-r") ; ugly special case as the library is called poly-R
  1622. (featurep (intern prefix)))))
  1623. (cons (replace-regexp-in-string abrev-regexp ""
  1624. (if is-lib
  1625. (replace-regexp-in-string "^poly-[^-]+-" "" str-nm)
  1626. str-nm))
  1627. str-nm)))
  1628. list))
  1629. (defun pm--object-value (obj)
  1630. (cond
  1631. ((functionp obj)
  1632. (funcall obj))
  1633. ((symbolp obj)
  1634. (symbol-value obj))
  1635. (t obj)))
  1636. (defun pm--oref-value (object slot)
  1637. (pm--object-value (eieio-oref object slot)))
  1638. (defun pm--prop-put (key val &optional object)
  1639. (oset (or object pm/polymode) -props
  1640. (plist-put (oref (or object pm/polymode) -props) key val)))
  1641. (defun pm--prop-get (key &optional object)
  1642. (plist-get (oref (or object pm/polymode) -props) key))
  1643. (defun pm--comment-region (beg end)
  1644. ;; mark as syntactic comment
  1645. (when (> end 1)
  1646. (with-silent-modifications
  1647. (let ((beg (or beg (region-beginning)))
  1648. (end (or end (region-end))))
  1649. (let ((ch-beg (char-after beg))
  1650. (ch-end (char-before end)))
  1651. (add-text-properties beg (1+ beg)
  1652. (list 'syntax-table (cons 11 ch-beg)
  1653. 'rear-nonsticky t
  1654. 'polymode-comment 'start))
  1655. (add-text-properties (1- end) end
  1656. (list 'syntax-table (cons 12 ch-end)
  1657. 'rear-nonsticky t
  1658. 'polymode-comment 'end)))))))
  1659. (defun pm--uncomment-region (beg end)
  1660. ;; Remove all syntax-table properties.
  1661. ;; fixme: this beggs for problems
  1662. (when (> end 1)
  1663. (with-silent-modifications
  1664. (let ((props '(syntax-table nil rear-nonsticky nil polymode-comment nil)))
  1665. (remove-text-properties (max beg (point-min)) (min end (point-max)) props)
  1666. ;; (remove-text-properties beg (1+ beg) props)
  1667. ;; (remove-text-properties end (1- end) props)
  1668. ))))
  1669. (defun pm--synchronize-points (&optional buffer)
  1670. "Synchronize the point in polymode buffers with the point in BUFFER."
  1671. (setq buffer (current-buffer))
  1672. (when (and polymode-mode
  1673. (buffer-live-p buffer))
  1674. (let* ((bufs (eieio-oref pm/polymode '-buffers))
  1675. ;; (buffer (or buffer
  1676. ;; (cl-loop for b in bufs
  1677. ;; if (and (buffer-live-p b)
  1678. ;; (buffer-local-value 'pm/current b))
  1679. ;; return b)
  1680. ;; (current-buffer)))
  1681. (pos (with-current-buffer buffer (point))))
  1682. (dolist (b bufs)
  1683. (when (buffer-live-p b)
  1684. (with-current-buffer b
  1685. (goto-char pos)))))))
  1686. (defun pm--completing-read (prompt collection &optional predicate require-match
  1687. initial-input hist def inherit-input-method)
  1688. ;; Wrapper for `completing-read'.
  1689. ;; Take care when collection is an alist of (name . meta-info). If
  1690. ;; so, asks for names, but returns meta-info for that name. Enforce
  1691. ;; require-match = t. Also takes care of adding the most relevant
  1692. ;; DEF from history.
  1693. (if (and (listp collection)
  1694. (listp (car collection)))
  1695. (let* ((candidates (mapcar #'car collection))
  1696. (thirst (and hist
  1697. (delq nil (mapcar (lambda (x) (car (member x candidates)))
  1698. (symbol-value hist)))))
  1699. (def (or def (car thirst) (car candidates))))
  1700. (assoc (completing-read prompt candidates predicate t initial-input hist def inherit-input-method)
  1701. collection))
  1702. (completing-read prompt collection predicate require-match initial-input hist def inherit-input-method)))
  1703. ;;; WEAVING and EXPORTING
  1704. ;; fixme: move all these into separate polymode-process.el?
  1705. (defvar polymode-exporter-output-file-format)
  1706. (defvar polymode-weaver-output-file-format)
  1707. (declare-function pm-export "polymode-export")
  1708. (declare-function pm-weave "polymode-weave")
  1709. (declare-function comint-exec "comint")
  1710. (declare-function comint-mode "comint")
  1711. (defun pm--wrap-callback (processor slot _ifile)
  1712. ;; replace processor :sentinel or :callback temporally in order to export-spec as a
  1713. ;; followup step or display the result
  1714. (let ((sentinel1 (eieio-oref processor slot))
  1715. (cur-dir default-directory)
  1716. (exporter (symbol-value (eieio-oref pm/polymode 'exporter)))
  1717. (obuffer (current-buffer)))
  1718. (if pm--export-spec
  1719. ;; 2-stage weaver->exporter
  1720. (let ((espec pm--export-spec))
  1721. (lambda (&rest args)
  1722. (with-current-buffer obuffer
  1723. (let ((wfile (apply sentinel1 args))
  1724. (pm--export-spec nil)
  1725. (pm--input-not-real t))
  1726. ;; If no wfile, probably errors occurred. So we stop.
  1727. (when wfile
  1728. (when (listp wfile)
  1729. ;; In an unlikely situation weaver can generate multiple
  1730. ;; files. Pick the first one.
  1731. (setq wfile (car wfile)))
  1732. (pm-export exporter (car espec) (cdr espec) wfile))))))
  1733. (lambda (&rest args)
  1734. (with-current-buffer obuffer
  1735. (let ((ofile (apply sentinel1 args)))
  1736. (when ofile
  1737. (let ((ofiles (if (listp ofile) ofile (list ofile))))
  1738. (dolist (f ofiles)
  1739. (pm--display-file (expand-file-name f cur-dir)))))))))))
  1740. (defun pm--file-mod-time (file)
  1741. (and (stringp file)
  1742. (file-exists-p file)
  1743. (nth 5 (file-attributes file))))
  1744. (defvar-local pm--process-buffer nil)
  1745. ;; Simplified version of TeX-run-TeX. Run shell COMMAND interactively in BUFFER.
  1746. ;; Run COMMAND in a buffer (in comint-shell-mode) in order to be able to accept
  1747. ;; user interaction.
  1748. (defun pm--run-shell-command (command sentinel buff-name message)
  1749. (require 'comint)
  1750. (let* ((buffer (get-buffer-create buff-name))
  1751. (process nil)
  1752. ;; weave/export buffers are re-usable; need to transfer some vars
  1753. (dd default-directory)
  1754. ;; (command (shell-quote-argument command))
  1755. (inhibit-read-only t))
  1756. (with-current-buffer buffer
  1757. (setq-local default-directory dd)
  1758. (setq buffer-read-only nil)
  1759. (erase-buffer)
  1760. (insert message)
  1761. (comint-exec buffer buff-name shell-file-name nil
  1762. (list shell-command-switch command))
  1763. (setq process (get-buffer-process buffer))
  1764. (comint-mode)
  1765. (goto-address-mode 1)
  1766. (set-process-sentinel process sentinel)
  1767. (setq pm--process-buffer t)
  1768. (set-marker (process-mark process) (point-max))
  1769. ;; for communication with sentinel
  1770. (process-put process :output-file pm--output-file)
  1771. (process-put process :output-file-mod-time (pm--file-mod-time pm--output-file))
  1772. (process-put process :input-file pm--input-file)
  1773. (when polymode-display-process-buffers
  1774. (display-buffer buffer `(nil . ((inhibit-same-window . ,pop-up-windows)))))
  1775. nil)))
  1776. (defun pm--make-shell-command-sentinel (action)
  1777. (lambda (process _name)
  1778. "Sentinel built with `pm--make-shell-command-sentinel'."
  1779. (let ((buff (process-buffer process))
  1780. (status (process-exit-status process)))
  1781. (if (> status 0)
  1782. (progn
  1783. (message "Errors during %s; process exit status %d" action status)
  1784. (ding) (sit-for 1)
  1785. nil)
  1786. (with-current-buffer buff
  1787. (let ((ofile (process-get process :output-file)))
  1788. (cond
  1789. ;; 1. output-file guesser
  1790. ((functionp ofile) (funcall ofile))
  1791. ;; 2. string
  1792. (ofile
  1793. (let ((otime (process-get process :output-file-mod-time))
  1794. (ntime (pm--file-mod-time ofile)))
  1795. (if (or (null ntime)
  1796. (and otime
  1797. (not (time-less-p otime ntime))))
  1798. ;; mod time didn't change
  1799. ;; tothink: shall we still return ofile for display?
  1800. (progn
  1801. (display-buffer (current-buffer))
  1802. (message "Output file unchanged. Either input unchanged or errors during %s." action)
  1803. (ding) (sit-for 1)
  1804. ofile)
  1805. ;; else, all is good, we return the file name
  1806. ;; (display-buffer (current-buffer))
  1807. (message "Done with %s" action)
  1808. ofile)))
  1809. ;; 3. output file is not known; display process buffer
  1810. (t (display-buffer (current-buffer)) nil))))))))
  1811. (fset 'pm-default-shell-export-sentinel (pm--make-shell-command-sentinel "export"))
  1812. (fset 'pm-default-shell-weave-sentinel (pm--make-shell-command-sentinel "weaving"))
  1813. (defun pm--make-selector (specs elements)
  1814. (cond ((functionp elements) elements)
  1815. ((listp elements)
  1816. (let ((spec-alist (cl-mapcar #'cons specs elements)))
  1817. (lambda (selsym &rest _ignore)
  1818. (cdr (assoc selsym spec-alist)))))
  1819. (t (error "Elements argument must be either a list or a function"))))
  1820. (defun pm--selector (processor type id)
  1821. (let ((spec (or (assoc id (eieio-oref processor type))
  1822. (error "%s spec '%s' cannot be found in '%s'"
  1823. (symbol-name type) id (eieio-object-name processor))))
  1824. (names (cond
  1825. ;; exporter slots
  1826. ((eq type :from) '(regexp doc command))
  1827. ((eq type :to) '(ext doc t-spec))
  1828. ;; weaver slot
  1829. ((eq type :from-to) '(regexp ext doc command))
  1830. (t (error "Invalid type '%s'" type)))))
  1831. (cons id (pm--make-selector names (cdr spec)))))
  1832. (defun pm--selector-match (el &optional file)
  1833. (let* ((id (car el))
  1834. (regexp (funcall (cdr el) 'regexp id)))
  1835. (or (funcall (cdr el) 'match id file)
  1836. (and regexp
  1837. (string-match-p regexp (or file buffer-file-name))))))
  1838. (defun pm--matched-selectors (translator slot)
  1839. (let ((translator (if (symbolp translator)
  1840. (symbol-value translator)
  1841. translator)))
  1842. (cl-loop for el in (pm--selectors translator slot)
  1843. when (pm--selector-match el)
  1844. collect el)))
  1845. (defun pm--selectors (processor type)
  1846. (let ((ids (mapcar #'car (eieio-oref processor type))))
  1847. (mapcar (lambda (id) (pm--selector processor type id)) ids)))
  1848. (defun pm--output-command.file (output-file-format sfrom &optional sto quote)
  1849. ;; !!Must be run in input buffer!!
  1850. (cl-flet ((squote (arg) (or (and (stringp arg)
  1851. (if quote (shell-quote-argument arg) arg))
  1852. "")))
  1853. (let* ((el (or sto sfrom))
  1854. (base-ofile (or (funcall (cdr el) 'output-file (car el))
  1855. (let ((ext (funcall (cdr el) 'ext (car el))))
  1856. (when ext
  1857. (concat (format output-file-format
  1858. (file-name-base buffer-file-name))
  1859. "." ext)))))
  1860. (ofile (and (stringp base-ofile)
  1861. (expand-file-name base-ofile)))
  1862. (oname (and (stringp base-ofile)
  1863. (file-name-base base-ofile)))
  1864. (t-spec (and sto (funcall (cdr sto) 't-spec (car sto))))
  1865. (command-w-formats (or (and sto (funcall (cdr sto) 'command (car sto)))
  1866. (and (listp t-spec) (car t-spec))
  1867. (funcall (cdr sfrom) 'command (car sfrom))))
  1868. (command (format-spec command-w-formats
  1869. (list (cons ?i (squote (file-name-nondirectory buffer-file-name)))
  1870. (cons ?I (squote buffer-file-name))
  1871. (cons ?o (squote base-ofile))
  1872. (cons ?O (squote ofile))
  1873. (cons ?b (squote oname))
  1874. (cons ?t (squote t-spec))))))
  1875. (cons command (or ofile base-ofile)))))
  1876. (defun pm--process-internal (processor from to ifile &optional callback quote)
  1877. (let ((is-exporter (object-of-class-p processor 'pm-exporter)))
  1878. (if is-exporter
  1879. (unless (and from to)
  1880. (error "For exporter both FROM and TO must be supplied (from: %s, to: %s)" from to))
  1881. (unless from
  1882. ;; it represents :from-to slot
  1883. (error "For weaver FROM must be supplied (from: %s)" from)))
  1884. (let* ((sfrom (if is-exporter
  1885. (pm--selector processor :from from)
  1886. (pm--selector processor :from-to from)))
  1887. (sto (and is-exporter (pm--selector processor :to to)))
  1888. (ifile (or ifile buffer-file-name))
  1889. ;; fixme: nowarn is only right for inputs from weavers, you need to
  1890. ;; save otherwise
  1891. (ibuffer (if pm--input-not-real
  1892. ;; for exporter input we silently re-fetch the file
  1893. ;; even if it was modified
  1894. (find-file-noselect ifile t)
  1895. ;; if real user file, get it or fetch it
  1896. (or (get-file-buffer ifile)
  1897. (find-file-noselect ifile))))
  1898. (output-format (if is-exporter
  1899. polymode-exporter-output-file-format
  1900. polymode-weaver-output-file-format)))
  1901. (when (buffer-live-p ibuffer)
  1902. (with-current-buffer ibuffer
  1903. ;; FIXME: could be deleted buffer in weaver->exporter pipeline?
  1904. (save-buffer)
  1905. (let ((comm.ofile (pm--output-command.file output-format sfrom sto quote)))
  1906. (let* ((pm--output-file (cdr comm.ofile))
  1907. (pm--input-file ifile)
  1908. ;; skip weaving step if possible
  1909. ;; :fixme this should not happen after weaver/exporter change
  1910. ;; or after errors in previous exporter
  1911. (omt (and polymode-skip-processing-when-unmodified
  1912. (stringp pm--output-file)
  1913. (pm--file-mod-time pm--output-file)))
  1914. (imt (and omt (pm--file-mod-time pm--input-file)))
  1915. (action (if is-exporter "exporting" "weaving"))
  1916. (ofile (if (and imt (time-less-p imt omt))
  1917. (progn
  1918. (message "Not re-%s as input file '%s' hasn't changed"
  1919. (file-name-nondirectory ifile) action)
  1920. pm--output-file)
  1921. (message "%s '%s' with '%s' ..."
  1922. (capitalize action)
  1923. (file-name-nondirectory ifile)
  1924. (eieio-object-name processor))
  1925. (let ((fn (with-no-warnings
  1926. (eieio-oref processor 'function)))
  1927. ;; `to` is nil for weavers
  1928. (args (delq nil (list from to)))
  1929. (comm (car comm.ofile)))
  1930. (if callback
  1931. ;; the display is handled within the
  1932. ;; callback and return value of :function
  1933. ;; slot is ignored
  1934. (progn (apply fn comm callback args)
  1935. nil)
  1936. (apply fn comm args))))))
  1937. (when ofile
  1938. (if pm--export-spec
  1939. ;; same logic as in pm--wrap-callback
  1940. (let ((pm--input-not-real t)
  1941. (espec pm--export-spec)
  1942. (pm--export-spec nil))
  1943. (when (listp ofile)
  1944. (setq ofile (car ofile)))
  1945. (pm-export (symbol-value (eieio-oref pm/polymode 'exporter))
  1946. (car espec) (cdr espec)
  1947. ofile))
  1948. (pm--display-file ofile))))))))))
  1949. ;; (defun replace-poly-spec ()
  1950. ;; (interactive)
  1951. ;; (when (re-search-forward "defcustom +pm-\\(inner\\|host\\|poly\\)/\\([^ \n]+\\)" nil t)
  1952. ;; (let* ((mode (match-string 2))
  1953. ;; (type (match-string 1))
  1954. ;; (new-name (format "poly-%s-%smode" mode type)))
  1955. ;; (previous-line 1)
  1956. ;; (insert (format "(define-obsolete-variable-alias 'pm-%s/%s '%s \"v0.2\")\n" type mode new-name))
  1957. ;; (insert (format "(define-%smode %s\n)" type new-name)))))
  1958. (provide 'polymode-core)
  1959. ;;; polymode-core.el ends here