(require 'gv)
(require 'font-lock)
(require 'color)
(require 'polymode-classes)
(require 'format-spec)
(require 'subr-x)
(eval-when-compile
(require 'cl-lib)
(require 'derived))
(defvar *span* nil)
(defvar-local pm/polymode nil)
(put 'pm/polymode 'permanent-local t)
(defvar-local pm/chunkmode nil)
(defvar-local pm/current nil) (defvar-local pm/type nil) (defvar-local polymode-mode nil
"Non-nil if current \"mode\" is a polymode.")
(defvar pm--emacs>26 (version<= "26" emacs-version))
(defvar-local pm--indent-region-function-original nil)
(defvar-local pm--fill-forward-paragraph-original nil)
(defvar-local pm--indent-line-function-original nil)
(defvar-local pm--syntax-propertize-function-original nil)
(defvar pm--output-file nil)
(defvar pm--input-buffer nil)
(defvar pm--input-file nil)
(defvar pm--export-spec nil)
(defvar pm--input-not-real nil)
(defvar pm--output-not-real nil)
(declare-function pm-initialize "polymode-methods")
(declare-function pm-get-buffer-of-mode "polymode-methods")
(declare-function pm-get-buffer-create "polymode-methods")
(declare-function pm-get-adjust-face "polymode-methods")
(declare-function pm-get-span "polymode-methods")
(declare-function pm-next-chunk "polymode-methods")
(eval-when-compile
(defclass dummy ()
((function) (from-to))))
(defun pm-object-name (obj)
(with-no-warnings
(eieio-object-name-string obj)))
(defvar pm-allow-after-change-hook t)
(defvar pm-allow-before-change-hook t)
(defvar pm-allow-pre-command-hook t)
(defvar pm-allow-post-command-hook t)
(defun polymode-disable-post-command ()
(when polymode-mode
(setq pm-allow-post-command-hook nil)))
(defun polymode-enable-post-command ()
(when polymode-mode
(setq pm-allow-post-command-hook t)))
(defvar pm-initialization-in-progress nil)
(defvar pm-hide-implementation-buffers t)
(defvar-local pm--core-buffer-name nil)
(defun pm--hidden-buffer-name ()
(generate-new-buffer-name (concat " " pm--core-buffer-name)))
(defun pm--visible-buffer-name ()
(generate-new-buffer-name
(replace-regexp-in-string "^ +" "" pm--core-buffer-name)))
(defvar-local polymode-default-inner-mode nil
"Inner mode for chunks with unspecified modes.
Intended to be used as local variable in polymode buffers. A
special value `host' means use the host mode.")
(put 'polymode-default-inner-mode 'safe-local-variable #'symbolp)
(defgroup polymode nil
"Object oriented framework for multiple modes based on indirect buffers"
:link '(emacs-commentary-link "polymode")
:group 'tools)
(defgroup poly-modes nil
"Polymode Configuration Objects"
:group 'polymode)
(defgroup poly-hostmodes nil
"Polymode Host Chunkmode Objects"
:group 'polymode)
(defgroup poly-innermodes nil
"Polymode Chunkmode Objects"
:group 'polymode)
(defcustom polymode-display-output-file t
"Whether to display woven and exported output buffers.
When non-nil automatically visit and call `display-buffer' on
output files from processor engines (e.g. weavers and exporters).
Can also be a function, in which case it is called with the
output file name as the only argument. If this function returns
non-nil, the file is visited and displayed with `display-buffer'.
See `display-buffer-alist' for how to customize the display."
:group 'polymode
:type '(choice (const t) (const nil) function))
(defcustom polymode-display-process-buffers t
"When non-nil, display weaving and exporting process buffers."
:group 'polymode
:type 'boolean)
(defcustom polymode-skip-processing-when-unmodified t
"If non-nil, consider modification times of input and output files.
Skip weaving or exporting process when output file is more recent
than the input file."
:group 'polymode
:type 'boolean)
(define-obsolete-variable-alias 'polymode-mode-name-override-alist 'polymode-mode-name-aliases "2018-08")
(define-obsolete-variable-alias 'polymode-mode-name-alias-alist 'polymode-mode-name-aliases "2019-04")
(defcustom polymode-mode-name-aliases
'(
(asymptote . asy-mode)
(bash . sh-mode)
(cpp . c++-mode)
(ditaa . artist-mode)
(el . emacs-lisp)
(elisp . emacs-lisp)
(ocaml . tuareg)
(screen . shell-script-mode)
(shell . sh-mode)
(sqlite . sql-mode)
)
"An alist of inner mode overrides.
When inner mode is automatically detected from the header of the
inner chunk (such as in markdown mode), the detected symbol might
not correspond to the desired mode. This alist maps discovered
symbols into desired modes. For example
(add-to-list 'polymode-mode-name-aliases '(julia . ess-julia))
will cause installation of `ess-julia-mode' in markdown ```julia chunks."
:group 'polymode
:type 'alist)
(defvar polymode-mode-abbrev-aliases nil
"An alist of abbreviation mappings from mode names to their abbreviations.
Used to compute mode post-fixes in buffer names. Example:
(add-to-list \\='polymode-mode-abbrevs-aliases \\='(\"ess-r\" . \"R\"))")
(defvar polymode-before-switch-buffer-hook nil
"Hook run just before switching to a different polymode buffer.
Each function is run with two arguments `old-buffer' and
`new-buffer'. This hook is commonly used to transfer state
between buffers. Hook is run before transfer of variables, modes
and overlays.")
(define-obsolete-variable-alias 'polymode-switch-buffer-hook 'polymode-after-switch-buffer-hook "v0.2")
(defvar polymode-after-switch-buffer-hook nil
"Hook run after switching to a different polymode buffer.
Each function is run with two arguments `old-buffer' and
`new-buffer'. This hook is commonly used to transfer state
between buffers. Slot :switch-buffer-functions in `pm-polymode'
and `pm-chunkmode' objects provides same functionality for
narrower scope.")
(defvar polymode-init-host-hook nil
"Hook run on initialization of every hostmode.
Ran in a base buffer from `pm-initialze'
methods. Slot :init-functions in `pm-polymode' objects provides
similar hook for more focused scope. See
`polymode-init-inner-hook' and :init-functions slot in
`pm-chunkmode' objects for similar hooks for inner chunkmodes.")
(defvar polymode-init-inner-hook nil
"Hook run on initialization of every `pm-chunkmode' object.
The hook is run in chunkmode's body buffer from `pm-initialze'
`pm-chunkmode' methods. Slot :init-functions `pm-chunkmode'
objects provides same functionality for narrower scope. See also
`polymode-init-host-hook'.")
(defun polymode--define-chunkmode (constructor name parent doc key-args)
(let* ((type (format "%smode"
(replace-regexp-in-string
"-.*$" "" (replace-regexp-in-string "^pm-" "" (symbol-name constructor)))))
(sname (symbol-name name))
(root-name (replace-regexp-in-string (format "poly-\\|-%s" type) "" sname)))
(when (keywordp parent)
(progn
(push doc key-args)
(push parent key-args)
(setq doc nil parent nil)))
(unless (stringp doc)
(when (keywordp doc)
(push doc key-args))
(setq doc (format "%s for %s chunks." (capitalize type) root-name)))
(unless (string-match-p (format "-%s$" type) sname)
(error "%s must end in '-%s'" (capitalize type) type))
(unless (symbolp parent)
(error "PARENT must be a name of an `%s'" type))
`(progn
(makunbound ',name)
(defvar ,name
,(if parent
`(pm--safe-clone ',constructor ,parent :name ,root-name ,@key-args)
`(,constructor :name ,root-name ,@key-args))
,doc))
))
(defmacro define-hostmode (name &optional parent doc &rest key-args)
"Define a hostmode with name NAME.
Optional PARENT is a name of a hostmode to be derived (cloned)
from. If missing, the optional documentation string DOC is
generated automatically. KEY-ARGS is a list of key-value pairs.
See the documentation of the class `pm-host-chunkmode' for
possible values."
(declare (doc-string 3) (indent defun))
(polymode--define-chunkmode 'pm-host-chunkmode name parent doc key-args))
(defmacro define-innermode (name &optional parent doc &rest key-args)
"Ddefine an innermode with name NAME.
Optional PARENT is a name of a innermode to be derived (cloned)
from. If missing the optional documentation string DOC is
generated automatically. KEY-ARGS is a list of key-value pairs.
See the documentation of the class `pm-inner-chunkmode' for
possible values."
(declare (doc-string 3) (indent defun))
(polymode--define-chunkmode 'pm-inner-chunkmode name parent doc key-args))
(defmacro define-auto-innermode (name &optional parent doc &rest key-args)
"Ddefine an auto innermode with name NAME.
Optional PARENT is a name of an auto innermode to be
derived (cloned) from. If missing the optional documentation
string DOC is generated automatically. KEY-ARGS is a list of
key-value pairs. See the documentation of the class
`pm-inner-auto-chunkmode' for possible values."
(declare (doc-string 3) (indent defun))
(polymode--define-chunkmode 'pm-inner-auto-chunkmode name parent doc key-args))
(defvar pm-extra-span-info nil)
(defun pm-format-span (&optional span prefixp)
(let* ((span (cond
((number-or-marker-p span) (pm-innermost-span span))
((null span) (pm-innermost-span))
(span)))
(message-log-max nil)
(beg (nth 1 span))
(end (nth 2 span))
(type (and span (or (car span) 'host)))
(oname (if span
(eieio-object-name (nth 3 span))
(current-buffer)))
(extra (if pm-extra-span-info
(format (if prefixp "%s " " (%s)") pm-extra-span-info)
"")))
(if prefixp
(format "%s[%s %s-%s %s]" extra type beg end oname)
(format "[%s %s-%s %s]%s" type beg end oname extra))))
(defsubst pm-base-buffer ()
"Return base buffer of current buffer, or the current buffer if it's direct."
(or (buffer-base-buffer (current-buffer))
(current-buffer)))
(defun pm-span-mode (&optional span)
"Retrieve the major mode associated with SPAN."
(pm--true-mode-symbol
(buffer-local-value 'major-mode (pm-span-buffer span))))
(defun pm-span-buffer (&optional span)
"Retrieve the buffer associated with SPAN."
(setq span (or span (pm-innermost-span)))
(let* ((chunkmode (nth 3 span))
(type (pm-true-span-type span)))
(if type
(pm-get-buffer-create chunkmode type)
(pm-get-buffer-create (oref pm/polymode -hostmode)))))
(defun pm-true-span-type (chunkmode &optional type)
"Retrieve the TYPE of buffer to be installed for CHUNKMODE.
`pm-innermost-span' returns a raw type (head, body or tail) but
the actual type installed depends on the values of :host-mode and
:tail-mode of the CHUNKMODE object. Always return nil if TYPE is
nil (aka a host span). CHUNKMODE could also be a span, in which
case TYPE is ignored."
(when (listp chunkmode)
(setq type (car chunkmode)
chunkmode (nth 3 chunkmode)))
(when (object-of-class-p chunkmode 'pm-inner-chunkmode)
(unless (or (null type) (eq type 'host))
(with-slots (mode head-mode tail-mode fallback-mode) chunkmode
(cond ((eq type 'body)
(unless (or (eq mode 'host)
(and (null mode)
(if polymode-default-inner-mode
(eq polymode-default-inner-mode 'host)
(eq fallback-mode 'host))))
'body))
((eq type 'head)
(cond ((eq head-mode 'host) nil)
((eq head-mode 'body) 'body)
(t 'head)))
((eq type 'tail)
(cond ((eq tail-mode 'host) nil)
((eq tail-mode 'body) 'body)
(t 'tail)))
(t (error "Type must be one of nil, 'host, 'head, 'tail or 'body")))))))
(defvar pm-use-cache t)
(defun pm-cache-span (span)
(when pm-use-cache
(unless pm-initialization-in-progress
(with-silent-modifications
(let ((sbeg (nth 1 span))
(send (nth 2 span)))
(put-text-property sbeg send :pm-span span)
(put-text-property sbeg send :pm-mode (pm-span-mode span)))))))
(defun pm-flush-span-cache (beg end &optional buffer)
(with-silent-modifications
(remove-list-of-text-properties beg end '(:pm-span) buffer)))
(defun pm--outspan-p (span thespan)
"Non-nil if SPAN outspans THESPAN.
Return non-nil if SPAN contains THESPAN's chunk (strictly from
the front)."
(let ((type (car thespan))
(beg (nth 1 thespan))
(end (nth 2 thespan))
(sbeg (nth 1 span))
(send (nth 2 span)))
(and
(< sbeg beg)
(cond
((eq type 'body)
(and (let ((hspan (pm-get-span (nth 3 thespan) (1- beg))))
(< sbeg (nth 1 hspan)))
(if (< end send)
(let ((tspan (pm-get-span (nth 3 thespan) (1+ end))))
(<= (nth 2 tspan) send))
(= end send))))
((eq type 'tail)
(let ((bspan (pm-get-span (nth 3 thespan) (1- beg))))
(when (< sbeg (nth 1 bspan))
(let ((hspan (pm-get-span (nth 3 thespan) (1- (nth 1 bspan)))))
(< sbeg (nth 1 hspan))))))
((eq type 'head)
(if (< end send)
(let ((bspan (pm-get-span (nth 3 thespan) (1+ end))))
(if (< (nth 2 bspan) send)
(let ((tspan (pm-get-span (nth 3 thespan) (1+ (nth 2 bspan)))))
(<= (nth 2 tspan) send))
(= (nth 2 bspan) send)))
(= end send)))))))
(defun pm--intersect-spans (thespan span)
(when span
(let ((allow-nested (eieio-oref (nth 3 span) 'allow-nested))
(is-host (null (car span))))
(cond
((or is-host (eq allow-nested 'always))
(if (car thespan)
(setq thespan
(list (car thespan)
(max (nth 1 span) (nth 1 thespan))
(min (nth 2 span) (nth 2 thespan))
(nth 3 thespan)))
(setq thespan
(list (car span)
(max (nth 1 span) (nth 1 thespan))
(min (nth 2 span) (nth 2 thespan))
(nth 3 (if is-host thespan span))))))
((and (>= (nth 1 span) (nth 1 thespan))
(<= (nth 2 span) (nth 2 thespan)))
(when (or (null (car thespan))
(eieio-oref (nth 3 span) 'can-nest))
(setq thespan span)))
((and (eq allow-nested t)
(car thespan) (not (eieio-oref (nth 3 thespan) 'can-nest))
(pm--outspan-p span thespan))
(setq thespan span)))))
thespan)
(defun pm--get-intersected-span (config &optional pos)
(let* ((start (point-min))
(end (point-max))
(pos (or pos (point)))
(hostmode (oref config -hostmode))
(chunkmodes (cons hostmode (oref config -innermodes)))
(thespan (list nil start end hostmode)))
(dolist (cm chunkmodes)
(setq thespan (pm--intersect-spans thespan (pm-get-span cm pos))))
(unless (and (<= start end) (<= pos end) (>= pos start))
(error "Bad polymode selection: span:%s pos:%s"
(list start end) pos))
(pm-cache-span thespan)
thespan))
(defun pm--chop-span (span beg end)
(when (> beg (nth 1 span))
(setcar (cdr span) beg))
(when (< end (nth 2 span))
(setcar (cddr span) end))
span)
(defun pm--innermost-span (config &optional pos)
(let ((pos (or pos (point)))
(omin (point-min))
(omax (point-max))
(parse-sexp-lookup-properties nil)
(case-fold-search t))
(save-excursion
(save-restriction
(widen)
(let ((span (pm--get-intersected-span config pos)))
(if (= omax pos)
(when (and (= omax (nth 1 span))
(> omax omin))
(setq span (pm--get-intersected-span config (1- pos))))
(when (= pos (nth 2 span))
(error "Span ends at %d in (pm--inermost-span %d) %s"
pos pos (pm-format-span span))))
(pm--chop-span span omin omax))))))
(defun pm--cached-span (&optional pos)
(unless pm-initialization-in-progress
(let* ((omin (point-min))
(omax (point-max))
(pos (or pos (point)))
(pos (if (= pos omax)
(max (point-min) (1- pos))
pos))
(span (get-text-property pos :pm-span)))
(when span
(save-restriction
(widen)
(let* ((beg (nth 1 span))
(end (1- (nth 2 span))))
(when (and (< end (point-max)) (<= pos end)
(<= beg pos)
(eq span (get-text-property beg :pm-span))
(eq span (get-text-property end :pm-span))
(not (eq span (get-text-property (1+ end) :pm-span)))
(or (= beg (point-min))
(not (eq span (get-text-property (1- beg) :pm-span)))))
(pm--chop-span (copy-sequence span) omin omax))))))))
(define-obsolete-function-alias 'pm-get-innermost-span #'pm-innermost-span "2018-08")
(defun pm-innermost-span (&optional pos no-cache)
"Get span object at POS.
If NO-CACHE is non-nil, don't use cache and force re-computation
of the span. Return a cons (type start end chunkmode). POS
defaults to point. Guarantied to return a non-empty span."
(when (and pos (or (< pos (point-min)) (> pos (point-max))))
(signal 'args-out-of-range
(list :pos pos
:point-min (point-min)
:point-max (point-max))))
(save-match-data
(or (when (and pm-use-cache (not no-cache))
(pm--cached-span pos))
(pm--innermost-span pm/polymode pos))))
(defun pm-span-to-range (span)
(and span (cons (nth 1 span) (nth 2 span))))
(define-obsolete-function-alias 'pm-get-innermost-range #'pm-innermost-range "2018-08")
(defun pm-innermost-range (&optional pos no-cache)
(pm-span-to-range (pm-innermost-span pos no-cache)))
(defun pm-fun-matcher (matcher)
"Make a function matcher given a MATCHER.
MATCHER is one of the forms accepted by \=`pm-inner-chunkmode''s
:head-matcher slot."
(cond
((stringp matcher)
(lambda (ahead)
(if (< ahead 0)
(if (re-search-backward matcher nil t)
(cons (match-beginning 0) (match-end 0)))
(if (re-search-forward matcher nil t)
(cons (match-beginning 0) (match-end 0))))))
((functionp matcher)
matcher)
((consp matcher)
(lambda (ahead)
(when (re-search-forward (car matcher) nil t ahead)
(cons (match-beginning (cdr matcher))
(match-end (cdr matcher))))))
(t (error "Head and tail matchers must be either regexp strings, cons cells or functions"))))
(defun pm-forward-sexp-tail-matcher (_arg)
"A simple tail matcher for a common closing-sexp character.
Use this matcher if an inner mode is delimited by a closing
construct like ${...}, xyz[...], html! {...} etc. In order to
match the tail `forward-sexp' is matched from HEAD-END - 1
position. ARG is ignored - always match forward."
(when (> (point) 0)
(backward-char 1)
(ignore-errors
(forward-sexp 1)
(cons (1- (point)) (point)))))
(defun pm-same-indent-tail-matcher (_arg)
"Get the end position of block with the higher indent than the current column.
Used as tail matcher for blocks identified by same indent. See
function `poly-slim-mode' for examples. ARG is ignored; always search
forward."
(let* ((cur-indent (current-indentation))
(cur-col (current-column))
(block-col (if (< cur-indent cur-col)
cur-indent
(1- cur-indent)))
(end (point-at-eol)))
(forward-line 1)
(while (and (not (eobp))
(or (looking-at-p "[ \t]*$")
(and (> (current-indentation) block-col)
(setq end (point-at-eol)))))
(forward-line 1))
(setq end (min (point-max) (1+ end)))
(cons end end)))
(defun pm--get-property-nearby (property accessor ahead)
(let ((ahead (> ahead 0)))
(let* ((pos (if ahead
(if (get-text-property (point) property)
(point)
(next-single-property-change (point) property))
(previous-single-property-change (point) property)))
(val (when pos
(or (get-text-property pos property)
(and (setq pos (previous-single-property-change pos property nil (point-min)))
(get-text-property pos property))))))
(when val
(if accessor
(let ((val (save-excursion
(goto-char pos)
(funcall accessor val))))
(cond
((numberp val) (cons val val))
((consp val) (cons (car val) (if (listp (cdr val))
(cadr val)
(cdr val))))
(t (cons pos (next-single-property-change pos property nil (point-max))))))
(cons pos (next-single-property-change pos property nil (point-max))))))))
(defun pm-make-text-property-matcher (property &optional accessor)
"Return a head or tail matcher for PROPERTY with ACCESSOR.
ACCESSOR is either a function or a keyword. When a function it is
applied to the PROPERTY's value to retrieve the position of the
head in the buffer. It should return either a number in which
case head has 0 length, a cons of the form (BEG . END), or a
list (BEG END). ACCESSOR is called at the beginning of the
PROPERTY region. When ACCESSOR is nil the head span is the region
covered by the same value of PROPERTY. When ACCESSOR is a keyword
the property is searched as when ACCESSOR is nil but is adapted
according to the keyword. Currently :inc-end means increment the
END of the span, when :dec-beg, decrement the beginning of the
span."
(lambda (ahead)
(if (keywordp accessor)
(let ((loc (pm--get-property-nearby property nil ahead)))
(when loc
(cond
((eq accessor :inc-end) (setcdr loc (1+ (cdr loc))))
((eq accessor :dec-beg) (setcar loc (1- (cdr loc))))
(t (error "Invalid ACCESSOR keyword")))
loc))
(pm--get-property-nearby property accessor ahead))))
(defun pm--span-at-point (head-matcher tail-matcher &optional pos can-overlap do-chunk)
"Span detector with head and tail matchers.
HEAD-MATCHER and TAIL-MATCHER is as in :head-matcher slot of
`pm-inner-chunkmode' object. POS defaults to (point). When
CAN-OVERLAP is non-nil nested chunks of this type are allowed.
Return a list of the form (TYPE SPAN-START SPAN-END) where TYPE
is one of the following symbols:
nil - pos is between ‘point-min’ and head-matcher, or between
tail-matcher and ‘point-max’
body - pos is between head-matcher and tail-matcher (exclusively)
head - head span
tail - tail span
Non-nil DO-CHUNK makes this function return a list of the
form (TYPE HEAD-START HEAD-END TAIL-START TAIL-END)."
(setq pos (or pos (point)))
(save-restriction
(widen)
(save-excursion
(goto-char pos)
(let* ((at-max (= pos (point-max)))
(head-matcher (pm-fun-matcher head-matcher))
(tail-matcher (pm-fun-matcher tail-matcher))
(head1 (funcall head-matcher -1)))
(if head1
(if (or (< pos (cdr head1))
(and at-max (= (cdr head1) pos)))
(if do-chunk
(pm--find-tail-from-head pos head1 head-matcher tail-matcher can-overlap 'head)
(list 'head (car head1) (cdr head1)))
(pm--find-tail-from-head pos head1 head-matcher tail-matcher can-overlap do-chunk))
(goto-char (point-min))
(let ((head2 (funcall head-matcher 1)))
(if head2
(if (< pos (car head2))
(if do-chunk
(list nil (point-min) (point-min) (car head2) (car head2))
(list nil (point-min) (car head2)))
(if (< pos (cdr head2))
(if do-chunk
(pm--find-tail-from-head pos head2 head-matcher tail-matcher can-overlap 'head)
(list 'head (car head2) (cdr head2)))
(pm--find-tail-from-head pos head2 head-matcher tail-matcher can-overlap do-chunk)))
nil)))))))
(defun pm--find-tail-from-head (pos head head-matcher tail-matcher can-overlap do-chunk)
(goto-char (cdr head))
(let ((tail (funcall tail-matcher 1))
(at-max (= pos (point-max)))
(type 'tail))
(when can-overlap
(save-excursion
(goto-char (cdr head))
(let ((match (funcall head-matcher 1)))
(when (or (null tail)
(and match (< (car match) (car tail))))
(setq tail match
type 'head)))))
(if tail
(if (< pos (car tail))
(if do-chunk
(list (if (eq do-chunk t) 'body do-chunk)
(car head) (cdr head) (car tail) (cdr tail))
(list 'body (cdr head) (car tail)))
(if (or (< pos (cdr tail))
(and at-max (= pos (cdr tail))))
(if do-chunk
(if (eq type 'tail)
(list (if (eq do-chunk t) 'tail do-chunk)
(car head) (cdr head) (car tail) (cdr tail))
(pm--find-tail-from-head pos tail head-matcher tail-matcher can-overlap do-chunk))
(list type (car tail) (cdr tail)))
(goto-char (cdr tail))
(let ((match (funcall head-matcher 1))
(type 'head))
(when can-overlap
(save-excursion
(goto-char (cdr tail))
(let ((match2 (funcall tail-matcher 1)))
(when (or (null match)
(and match2 (< (car match2) (car match))))
(setq match match2
type 'tail)))))
(if match
(if (< pos (car match))
(if do-chunk
(list nil (cdr tail) (cdr tail) (car match) (car match))
(list nil (cdr tail) (car match)))
(if (or (< pos (cdr match))
(and at-max (= pos (cdr match))))
(if do-chunk
(if (eq type 'tail)
(list (if (eq do-chunk t) 'tail do-chunk)
(car head) (cdr head) (car match) (cdr match))
(pm--find-tail-from-head pos match head-matcher tail-matcher can-overlap 'head))
(list type (car match) (cdr match)))
(pm--find-tail-from-head pos match head-matcher tail-matcher can-overlap do-chunk)))
(if do-chunk
(list nil (cdr tail) (cdr tail) (point-max) (point-max))
(list nil (cdr tail) (point-max)))))))
(if do-chunk
(list (if (eq do-chunk t) 'body do-chunk) (cdr head) (cdr head) (point-max) (point-max))
(list 'body (cdr head) (point-max))))))
(defun pm--next-chunk (head-matcher tail-matcher &optional pos can-overlap)
"Forward only span detector.
For HEAD-MATCHER, TAIL-MATCHER, POS and CAN-OVERLAP see
`pm--span-at-point'. Return a list of the form (HEAD-START
HEAD-END TAIL-START TAIL-END). Can return nil if there are no
forward spans from pos."
(setq pos (or pos (point)))
(save-restriction
(widen)
(save-excursion
(goto-char pos)
(let ((parse-sexp-lookup-properties nil)
(case-fold-search t)
(head-matcher (pm-fun-matcher head-matcher))
(tail-matcher (pm-fun-matcher tail-matcher))
(head nil))
(forward-line 0)
(setq head (funcall head-matcher 1))
(while (and head (< (car head) pos))
(setq head (funcall head-matcher 1)))
(when head
(goto-char (cdr head))
(let ((tail (or (funcall tail-matcher 1)
(cons (point-max) (point-max)))))
(when can-overlap
(goto-char (cdr head))
(when-let ((hbeg (car (funcall head-matcher 1))))
(when (< hbeg (car tail))
(setq tail (cons hbeg hbeg)))))
(list (car head) (cdr head) (car tail) (cdr tail))))))))
(defun pm-goto-span-of-type (type N)
"Skip to N - 1 spans of TYPE and stop at the start of a span of TYPE.
TYPE is either a symbol or a list of symbols of span types."
(let* ((sofar 0)
(types (if (symbolp type)
(list type)
type))
(back (< N 0))
(N (if back (- N) N))
(beg (if back (point-min) (point)))
(end (if back (point) (point-max))))
(unless (memq (car (pm-innermost-span)) types)
(setq sofar 1))
(condition-case nil
(pm-map-over-spans
(lambda (span)
(when (memq (car span) types)
(goto-char (nth 1 span))
(when (>= sofar N)
(signal 'quit nil))
(setq sofar (1+ sofar))))
beg end nil back)
(quit nil))
sofar))
(defun pm--run-derived-mode-hooks ()
(let* ((this-mode (eieio-oref pm/polymode '-minor-mode))
(this-state (symbol-value this-mode)))
(mapc (lambda (mm)
(let ((old-state (symbol-value mm)))
(unwind-protect
(progn
(set mm this-state)
(run-hooks (derived-mode-hook-name mm)))
(set mm old-state))))
(pm--collect-parent-slots pm/polymode '-minor-mode))))
(defun pm--run-init-hooks (object type &optional global-hook)
(unless pm-initialization-in-progress
(when global-hook
(run-hooks global-hook))
(pm--run-hooks object :init-functions (or type 'host))))
(defun pm--collect-parent-slots (object slot &optional do-when inclusive)
"Descend into parents of OBJECT and return a list of SLOT values.
Returned list is in parent first order. If non-nil DO-WHEN must
be a function which would take an object and return non-nil if
the recursion should descend into the parent. When nil, all
parents are descended. If INCLUSIVE is non-nil, include the slot
of the first object for which DO-WHEN failed."
(let ((inst object)
(vals nil)
(failed nil))
(while inst
(if (not (slot-boundp inst slot))
(setq inst (and (slot-boundp inst :parent-instance)
(eieio-oref inst 'parent-instance)))
(push (eieio-oref inst slot) vals)
(setq inst (and
(or (null do-when)
(if failed
(progn (setq failed nil) t)
(or (funcall do-when inst)
(and inclusive
(setq failed t)))))
(slot-boundp inst :parent-instance)
(eieio-oref inst 'parent-instance)))))
vals))
(defun pm--run-hooks (object slot &rest args)
"Run hooks from SLOT of OBJECT and its parent instances.
Parents' hooks are run first."
(let ((funs (delete-dups
(copy-sequence
(apply #'append
(pm--collect-parent-slots object slot))))))
(if args
(mapc (lambda (fn)
(apply fn args))
funs)
(mapc #'funcall funs))))
(define-obsolete-variable-alias 'pm-move-vars-from-base 'polymode-move-these-vars-from-base-buffer "v0.1.6")
(defvar polymode-move-these-vars-from-base-buffer
'(buffer-file-name
buffer-display-table
outline-regexp
outline-level
polymode-default-inner-mode
tab-width)
"Variables transferred from base buffer on switch to inner mode buffer.")
(define-obsolete-variable-alias 'pm-move-vars-from-old-buffer 'polymode-move-these-vars-from-old-buffer "v0.1.6")
(defvar polymode-move-these-vars-from-old-buffer
'(buffer-face-mode
buffer-face-mode-face
buffer-face-mode-remapping
buffer-invisibility-spec
buffer-read-only
buffer-undo-list
buffer-undo-tree
display-line-numbers
face-remapping-alist
isearch-mode line-move-visual
left-margin-width
right-margin-width
overwrite-mode
selective-display
text-scale-mode
text-scale-mode-amount
transient-mark-mode
truncate-lines
truncate-partial-width-windows
word-wrap
mc--this-command)
"Variables transferred from old buffer to new buffer on buffer switch.")
(defvar polymode-move-these-minor-modes-from-base-buffer nil
"Minor modes to move from base buffer on buffer switch.")
(defvar polymode-move-these-minor-modes-from-old-buffer
'(linum-mode
visual-line-mode
visual-fill-column-mode
writeroom-mode
multiple-cursors-mode)
"Minor modes to move from the old buffer during buffer switch.")
(defun pm-own-buffer-p (&optional buffer)
"Return t if BUFFER is owned by polymode.
Owning a buffer means that the BUFFER is either the base buffer
or an indirect implementation buffer. If nil, the buffer was
created outside of polymode with `clone-indirect-buffer'."
(when pm/polymode
(memq (or buffer (current-buffer))
(eieio-oref pm/polymode '-buffers))))
(defun pm-select-buffer (span &optional visibly)
"Select the buffer associated with SPAN.
Install a new indirect buffer if it is not already installed.
Chunkmode's class should define `pm-get-buffer-create' method. If
VISIBLY is non-nil perform extra adjustment for \"visual\" buffer
switch."
(let ((buffer (pm-span-buffer span))
(own (pm-own-buffer-p))
(cbuf (current-buffer)))
(with-current-buffer buffer
(pm--reset-ppss-cache span))
(when (and own visibly)
(pm--synchronize-points cbuf)
(let ((mode (or (eieio-oref (nth 3 span) 'keep-in-mode)
(eieio-oref pm/polymode 'keep-in-mode))))
(setq buffer (cond
((null mode) buffer)
((eq mode 'host) (pm-base-buffer))
(mode (or (pm-get-buffer-of-mode mode)
buffer))))))
(unless (eq buffer cbuf)
(when (and own visibly)
(run-hook-with-args 'polymode-before-switch-buffer-hook
cbuf buffer))
(pm--move-vars polymode-move-these-vars-from-base-buffer
(pm-base-buffer) buffer)
(pm--move-vars polymode-move-these-vars-from-old-buffer
cbuf buffer)
(if visibly
(when own
(pm--select-existing-buffer-visibly buffer))
(set-buffer buffer)))))
(defvar text-scale-mode)
(defvar text-scale-mode-amount)
(defun pm--select-existing-buffer-visibly (new-buffer)
(let ((old-buffer (current-buffer))
(point (point))
(window-start (window-start))
(visible (pos-visible-in-window-p))
(ractive (region-active-p))
(mkt (mark t))
(hlf header-line-format))
(when pm-hide-implementation-buffers
(rename-buffer (pm--hidden-buffer-name)))
(setq pm/current nil)
(pm--move-minor-modes polymode-move-these-minor-modes-from-base-buffer
(pm-base-buffer) new-buffer)
(pm--move-minor-modes polymode-move-these-minor-modes-from-old-buffer
old-buffer new-buffer)
(pm--move-overlays old-buffer new-buffer)
(switch-to-buffer new-buffer)
(bury-buffer-internal old-buffer)
(set-window-prev-buffers nil (assq-delete-all old-buffer (window-prev-buffers nil)))
(unless header-line-format
(when hlf
(setq header-line-format '(""))))
(setq pm/current t)
(if (not ractive)
(deactivate-mark)
(set-mark mkt)
(activate-mark))
(when pm-hide-implementation-buffers
(rename-buffer (pm--visible-buffer-name)))
(goto-char point)
(when visible
(set-window-start (get-buffer-window new-buffer t) window-start))
(run-hook-with-args 'polymode-after-switch-buffer-hook old-buffer new-buffer)
(pm--run-hooks pm/polymode :switch-buffer-functions old-buffer new-buffer)
(pm--run-hooks pm/chunkmode :switch-buffer-functions old-buffer new-buffer)))
(defun pm--move-overlays (from-buffer to-buffer)
(with-current-buffer from-buffer
(mapc (lambda (o)
(unless (or (overlay-get o 'linum-str)
(overlay-get o 'yas--snippet))
(move-overlay o (overlay-start o) (overlay-end o) to-buffer)))
(overlays-in 1 (1+ (buffer-size))))))
(defun pm--move-vars (vars from-buffer &optional to-buffer)
(let ((to-buffer (or to-buffer (current-buffer))))
(unless (eq to-buffer from-buffer)
(with-current-buffer to-buffer
(dolist (var vars)
(when (default-boundp var)
(make-local-variable var)
(set var (buffer-local-value var from-buffer))))))))
(defun pm--move-minor-modes (modes from-buffer &optional to-buffer)
(let ((to-buffer (or to-buffer (current-buffer))))
(unless (eq to-buffer from-buffer)
(with-current-buffer to-buffer
(dolist (m modes)
(when (default-boundp m)
(let ((from-state (buffer-local-value m from-buffer)))
(unless (equal from-state (symbol-value m))
(funcall (symbol-function m) (if from-state 1 -1))))))))))
(defun pm-set-buffer (&optional pos-or-span)
"Set buffer to polymode buffer appropriate for POS-OR-SPAN.
This is done with `set-buffer' and no visual adjustments (like
overlay transport) are done. See `pm-switch-to-buffer' for a more
comprehensive alternative."
(let ((span (if (or (null pos-or-span)
(number-or-marker-p pos-or-span))
(pm-innermost-span pos-or-span)
pos-or-span)))
(pm-select-buffer span)))
(defun pm-switch-to-buffer (&optional pos-or-span)
"Bring the appropriate polymode buffer to front.
POS-OR-SPAN can be either a position in a buffer or a span. All
expensive adjustment for a visible switch (like overlay
transport) are performed."
(let ((span (if (or (null pos-or-span)
(number-or-marker-p pos-or-span))
(pm-innermost-span pos-or-span)
pos-or-span)))
(pm-select-buffer span 'visibly)))
(defun pm-map-over-modes (fn beg end)
"Apply function FN for each major mode between BEG and END.
FN is a function of two arguments mode-beg and mode-end. This is
different from `pm-map-over-spans' which maps over polymode
spans. Two adjacent spans might have same major mode, thus
`pm-map-over-modes' will iterate over same or bigger regions than
`pm-map-over-spans'."
(when (< beg end)
(save-restriction
(widen)
(let* ((hostmode (eieio-oref pm/polymode '-hostmode))
(pos beg)
(ttype 'dummy)
(span (pm-innermost-span beg))
(nspan span)
(ttype (pm-true-span-type span))
(nttype ttype))
(setq beg (nth 1 span)
pos (nth 2 span))
(while (and (< pos end)
(memq (car span) '(head body)))
(while (and (< pos end)
(eq ttype nttype))
(setq pos (nth 2 nspan)
nspan (pm-innermost-span pos)
nttype (pm-true-span-type nspan)))
(with-current-buffer (pm-span-buffer span)
(funcall fn beg pos))
(setq span nspan
ttype nttype
beg (nth 1 nspan)
pos (nth 2 nspan)))
(when (< pos end)
(let ((echunks (cl-loop for im in (eieio-oref pm/polymode '-innermodes)
collect (cons im nil)))
spans)
(while (< pos end)
(let (tchunks)
(dolist (echunk echunks)
(if (and (cdr echunk)
(< pos (nth 5 echunk)))
(push echunk tchunks)
(let ((nchunk (pm-next-chunk (car echunk) pos)))
(if nchunk
(push (cons (car echunk) nchunk) tchunks)
(when (cdr echunk)
(push echunk tchunks))))))
(setq echunks (reverse tchunks)))
(setq spans nil)
(dolist (echunk echunks)
(let ((chunk (cdr echunk)))
(let ((s (cond
((< pos (nth 1 chunk)) (list nil pos (nth 1 chunk) (car chunk)))
((< pos (nth 2 chunk)) (list 'head (nth 1 chunk) (nth 2 chunk) (car chunk)))
((< pos (nth 3 chunk)) (list 'body (nth 2 chunk) (nth 3 chunk) (car chunk)))
((< pos (nth 4 chunk)) (list 'tail (nth 3 chunk) (nth 4 chunk) (car chunk)))
(t (list nil (nth 4 chunk) (point-max) (car chunk))))))
(push s spans))))
(setq spans (nreverse spans))
(setq nspan (list nil pos (point-max) hostmode))
(dolist (s spans)
(setq nspan (pm--intersect-spans nspan s)))
(pm-cache-span nspan)
(setq nttype (pm-true-span-type nspan))
(unless (eq ttype nttype)
(with-current-buffer (pm-span-buffer span)
(funcall fn beg pos))
(setq ttype nttype
beg (nth 1 nspan)))
(setq span nspan
pos (nth 2 nspan)))))
(with-current-buffer (pm-span-buffer span)
(funcall fn beg pos))))))
(defun pm-map-over-spans (fun &optional beg end count backwardp visibly no-cache)
"For all spans between BEG and END, execute FUN.
FUN is a function of one argument a span object (also available
in a dynamic variable *span*). Buffer is *not* narrowed to the
span, nor point is moved. If COUNT is non-nil, jump at most that
many times. If BACKWARDP is non-nil, map backwards. If VISIBLY is
non-nil select buffers with the full synchronization (as if
performed by the user), otherwise point synchronization across
indirect buffers is not taken care of. Modification of the buffer
during mapping is an undefined behavior."
(save-restriction
(widen)
(setq beg (or beg (point-min))
end (if end
(min end (point-max))
(point-max)))
(unless count
(setq count most-positive-fixnum))
(let* ((nr 0)
(pos (if backwardp end beg))
(*span* (pm-innermost-span pos no-cache)))
(while *span*
(setq nr (1+ nr))
(pm-select-buffer *span* visibly)
(funcall fun *span*)
(setq pos
(if backwardp
(max 1 (1- (nth 1 *span*)))
(min (point-max) (nth 2 *span*))))
(setq *span*
(and (if backwardp
(> pos beg)
(< pos end))
(< nr count)
(pm-innermost-span pos no-cache)))))))
(defun pm-narrow-to-span (&optional span)
"Narrow to current SPAN."
(interactive)
(unless (= (point-min) (point-max))
(let ((span (or span
(pm-innermost-span))))
(let ((sbeg (nth 1 span))
(send (nth 2 span)))
(unless pm--emacs>26
(pm--reset-ppss-cache span))
(narrow-to-region sbeg send)))))
(defmacro pm-with-narrowed-to-span (span &rest body)
(declare (indent 1) (debug (sexp body)))
`(save-restriction
(pm-narrow-to-span ,span)
,@body))
(defun polymode-flush-syntax-ppss-cache (beg end _)
"Run `syntax-ppss-flush-cache' from BEG to END in all polymode buffers.
Placed with high priority in `after-change-functions' hook."
(dolist (buff (oref pm/polymode -buffers))
(when (buffer-live-p buff)
(with-current-buffer buff
(when (memq #'syntax-ppss-flush-cache before-change-functions)
(remove-hook 'before-change-functions #'syntax-ppss-flush-cache t))
(unless (eq (car after-change-functions)
#'polymode-flush-syntax-ppss-cache)
(delq #'polymode-flush-syntax-ppss-cache after-change-functions)
(setq after-change-functions (cons #'polymode-flush-syntax-ppss-cache
after-change-functions)))
(syntax-ppss-flush-cache beg end)
))))
(defun pm--run-other-hooks (allow syms hook &rest args)
(when (and allow polymode-mode pm/polymode)
(dolist (sym syms)
(dolist (buf (eieio-oref pm/polymode '-buffers))
(when (buffer-live-p buf)
(unless (eq buf (current-buffer))
(with-current-buffer buf
(when (memq sym (symbol-value hook))
(if args
(apply sym args)
(funcall sym))))))))))
(defvar polymode-run-these-before-save-functions-in-other-buffers nil
"Beore-save functions to run in indirect buffers.
Saving happens from the base buffer, thus only `before-save-hook'
declared in the base buffer is triggered.")
(defvar polymode-run-these-after-save-functions-in-other-buffers nil
"After-save functions to run in indirect buffers.
Saving happens from the base buffer, thus only `after-save-hook'
declared in the base buffer is triggered.")
(defun polymode-before-save ()
"Run after-save-hooks in indirect buffers.
Only those in `polymode-run-these-after-save-functions-in-other-buffers'
are triggered if present."
(pm--run-other-hooks t
polymode-run-these-before-save-functions-in-other-buffers
'after-save-hook))
(defun polymode-after-save ()
"Run after-save-hooks in indirect buffers.
Only those in `polymode-run-these-after-save-functions-in-other-buffers'
are triggered if present."
(pm--run-other-hooks t
polymode-run-these-after-save-functions-in-other-buffers
'after-save-hook))
(defvar polymode-run-these-before-change-functions-in-other-buffers nil
"Before-change functions to run in all other buffers.")
(defvar polymode-run-these-after-change-functions-in-other-buffers nil
"After-change functions to run in all other buffers.")
(defun polymode-before-change (beg end)
"Polymode before-change fixes.
Run `polymode-run-these-before-change-functions-in-other-buffers'.
Placed with low priority in `before-change-functions' hook."
(pm--prop-put :before-change-range (cons beg end))
(when (boundp 'lsp-mode)
(dolist (buf (eieio-oref pm/polymode '-buffers))
(with-current-buffer buf
(when lsp-mode
(setq pm--lsp-before-change-end-position (pm--lsp-position end))))))
(pm--run-other-hooks pm-allow-before-change-hook
polymode-run-these-before-change-functions-in-other-buffers
'before-change-functions
beg end))
(defun polymode-after-change (beg end len)
"Polymode after-change fixes.
Run `polymode-run-these-after-change-functions-in-other-buffers'.
Placed with low priority in `after-change-functions' hook."
(pm--run-other-hooks pm-allow-after-change-hook
polymode-run-these-after-change-functions-in-other-buffers
'after-change-functions
beg end len))
(defvar polymode-run-these-pre-commands-in-other-buffers nil
"These commands, if present in `pre-command-hook', are run in other bufers.")
(defvar polymode-run-these-post-commands-in-other-buffers nil
"These commands, if present in `post-command-hook', are run in other bufers.")
(defun polymode-pre-command ()
"Synchronize state between buffers and run pre-commands in other buffers.
Currently synchronize points and runs
`polymode-run-these-pre-commands-in-other-buffers' if any. Runs in
local `pre-command-hook' with very high priority."
(pm--synchronize-points (current-buffer))
(condition-case err
(pm--run-other-hooks pm-allow-pre-command-hook
polymode-run-these-pre-commands-in-other-buffers
'pre-command-hook)
(error (message "error polymode-pre-command run other hooks: (%s) %s"
(point) (error-message-string err)))))
(defun polymode-post-command ()
"Select the buffer relevant buffer and run post-commands in other buffers.
Run all the `post-command-hooks' in the new buffer and those
command defined in
`polymode-run-these-post-commands-in-other-buffers' whenever
appropriate. This function is placed into local
`post-command-hook' with very low priority."
(when (and pm-allow-post-command-hook
polymode-mode
pm/polymode)
(let ((cbuf (current-buffer)))
(condition-case err
(pm-switch-to-buffer)
(error (message "error in polymode-post-command: (pm-switch-to-buffer %s): %s"
(point) (error-message-string err))))
(condition-case err
(if (eq cbuf (current-buffer))
(pm--run-other-hooks pm-allow-post-command-hook
polymode-run-these-post-commands-in-other-buffers
'post-command-hook)
(run-hooks 'post-command-hook))
(error (message "error in polymode-post-command run other hooks: (%s) %s"
(point) (error-message-string err)))))))
(defvar-local pm--killed nil)
(defun polymode-after-kill-fixes ()
"Various fixes for polymode indirect buffers."
(when (pm-own-buffer-p)
(let ((base (pm-base-buffer)))
(set-buffer-modified-p nil)
(dolist (b (buffer-list))
(when (and (buffer-live-p b)
(eq (buffer-base-buffer b) base))
(with-current-buffer b
(setq pm--killed t)
(setq buffer-file-name nil)
(setq buffer-file-number nil)
(setq buffer-file-truename nil)))))))
(defun pm-turn-polymode-off (&optional new-mode)
"Remove all polymode indirect buffers and install NEW-MODE if any.
NEW-MODE can be t in which case mode is picked from the
`pm/polymode' object."
(when pm/polymode
(let* ((base (pm-base-buffer))
(mmode (buffer-local-value 'major-mode base))
(kill-buffer-hook (delete 'polymode-after-kill-fixes (copy-sequence kill-buffer-hook))))
(dolist (b (eieio-oref pm/polymode '-buffers))
(unless (eq b base)
(kill-buffer b)))
(with-current-buffer base
(setq pm/polymode nil)
(when new-mode
(if (eq new-mode t)
(funcall mmode)
(funcall new-mode)))))))
(defun polymode-after-change-major-mode-cleanup ()
"Remove all polymode implementation buffers on mode change."
(when (and pm/polymode (not polymode-mode))
(let* ((base (pm-base-buffer))
(mmode (unless (eq base (current-buffer))
major-mode)))
(unless (eq base (current-buffer))
(when (eq (window-buffer) (current-buffer))
(switch-to-buffer base)))
(pm-turn-polymode-off mmode))))
(add-hook 'after-change-major-mode-hook #'polymode-after-change-major-mode-cleanup)
(defun pm-around-advice (fun advice)
"Apply around ADVICE to FUN.
If FUN is a list, apply ADVICE to each element of it."
(cond ((listp fun)
(dolist (el fun) (pm-around-advice el advice)))
((and (symbolp fun)
(not (advice-member-p advice fun)))
(advice-add fun :around advice))))
(defun polymode-inhibit-during-initialization (orig-fun &rest args)
"Don't run ORIG-FUN (with ARGS) during polymode setup."
(unless pm-initialization-in-progress
(apply orig-fun args)))
(defun polymode-inhibit-in-indirect-buffers (orig-fun &rest args)
"Don't run ORIG-FUN (with ARGS) in polymode indirect buffers (aka inner modes).
Use this function to around advice delicate functions:
(advice-add \\='xyz :around #\\='polymode-inhibit-in-indirect-buffers)
or with `pm-around-advice' which allows for multiple advises at once:
(pm-around-advice \\='(foo bar) #\\='polymode-inhibit-in-indirect-buffers)"
(unless (and polymode-mode (buffer-base-buffer))
(apply orig-fun args)))
(defun polymode-with-current-base-buffer (orig-fun &rest args)
"Switch to base buffer and apply ORIG-FUN to ARGS.
Use this function to around advice of functions that should run
in base buffer only like this:
(advice-add \\='foo :around #\\='polymode-with-current-base-buffer)
or with `pm-around-advice' which allows for multiple advises at
once:
(pm-around-advice \\='(foo bar) #\\='polymode-with-current-base-buffer)"
(if (and polymode-mode
(not pm--killed)
(buffer-live-p (buffer-base-buffer)))
(let ( (cur-buf (current-buffer))
(base (buffer-base-buffer))
(first-arg (car-safe args)))
(prog1 (with-current-buffer base
(if (or (eq first-arg cur-buf)
(equal first-arg (buffer-name cur-buf)))
(apply orig-fun base (cdr args))
(apply orig-fun args)))
(when pm/polymode
(pm--synchronize-points base))))
(apply orig-fun args)))
(pm-around-advice 'find-alternate-file #'polymode-with-current-base-buffer)
(pm-around-advice 'write-file #'polymode-with-current-base-buffer)
(pm-around-advice 'basic-save-buffer #'polymode-with-current-base-buffer)
(defun polymode-fill-forward-paragraph (&optional arg)
"Function for `fill-forward-paragraph-function'.
ARG is the same as in `forward-paragraph'"
(let* ((neg (< arg 0))
(cur-span (pm-innermost-span (if neg (1- (point)) (point))))
(cur-mode (pm-span-mode cur-span))
(out (funcall (or pm--fill-forward-paragraph-original
#'forward-paragraph)
arg))
(new-mode (pm-span-mode (pm-innermost-span (point)))))
(unless (eq cur-mode new-mode)
(pm-goto-span-of-type (car cur-span) (if neg 1 -1)))
out))
(defun pm--call-syntax-propertize-original (start end)
(condition-case err
(save-excursion
(funcall pm--syntax-propertize-function-original start end))
(error
(message "ERROR: (%s %d %d) -> %s"
(if (symbolp pm--syntax-propertize-function-original)
pm--syntax-propertize-function-original
(format "polymode-syntax-propertize:%s" major-mode))
start end
(error-message-string err)))))
(defun polymode-syntax-propertize-extend-region-in-host (start end)
(let ((base (pm-base-buffer))
(min (point-min))
(max (point-max)))
(when base
(with-current-buffer base
(save-restriction
(narrow-to-region min max)
(let ((funs syntax-propertize-extend-region-functions)
(extended nil))
(while funs
(let* ((syntax-propertize--done most-positive-fixnum)
(fn (pop funs))
(new (unless (eq fn 'syntax-propertize-wholelines)
(funcall fn start end))))
(when (and new
(or (< (car new) start)
(> (cdr new) end)))
(setq extended t
start (car new)
end (cdr new))
(unless (eq funs (cdr syntax-propertize-extend-region-functions))
(setq funs syntax-propertize-extend-region-functions)))))
(when extended (cons start end))))))))
(defun pm--syntax-after (pos)
(let ((syntax (syntax-after pos)))
(with-temp-buffer
(internal-describe-syntax-value syntax)
(buffer-string))))
(defun polymode-syntax-propertize (beg end)
(unless pm-initialization-in-progress
(save-restriction
(widen)
(save-excursion
(save-match-data
(let ((base (pm-base-buffer))
(protect-host (with-current-buffer (pm-base-buffer)
(eieio-oref pm/chunkmode 'protect-syntax))))
(unless protect-host
(with-current-buffer base
(set 'syntax-propertize--done end)
(when pm--syntax-propertize-function-original
(pm--call-syntax-propertize-original beg end))))
(let ((last-ppss nil))
(pm-map-over-modes
(lambda (mbeg mend)
(set 'syntax-propertize--done (max end mend))
(if (eq base (current-buffer))
(when protect-host
(pm--reset-ppss-cache-0 mbeg last-ppss)
(when pm--syntax-propertize-function-original
(pm--call-syntax-propertize-original (max beg mbeg) mend))
(setq last-ppss (syntax-ppss mend)))
(pm--reset-ppss-cache-0 mbeg)
(when pm--syntax-propertize-function-original
(pm--call-syntax-propertize-original (max beg mbeg) mend))))
beg end))))))))
(defvar syntax-ppss-wide)
(defvar syntax-ppss-last)
(defvar syntax-ppss-cache)
(defun pm--reset-ppss-cache (span)
"Reset `syntax-ppss-last' cache if it was recorded before SPAN's start."
(let ((sbeg (nth 1 span))
new-ppss)
(unless (car span)
(save-restriction
(widen)
(save-excursion
(let ((pos sbeg))
(while (and (null new-ppss)
(not (= pos (point-min))))
(let ((prev-span (pm-innermost-span (1- pos))))
(if (null (car prev-span))
(setq new-ppss (syntax-ppss pos))
(setq pos (nth 1 prev-span)))))))))
(pm--reset-ppss-cache-0 sbeg new-ppss)))
(defun pm--reset-ppss-cache-0 (pos &optional new-ppss)
(unless new-ppss
(setq new-ppss (list 0 nil pos nil nil nil 0 nil nil nil nil)))
(let ((cache (if pm--emacs>26
(cdr syntax-ppss-wide)
syntax-ppss-cache)))
(while (and cache (>= (caar cache) pos))
(setq cache (cdr cache)))
(setq cache (cons (cons pos new-ppss) cache))
(if pm--emacs>26
(setq syntax-ppss-wide (cons (car cache) cache))
(setq syntax-ppss-cache cache)
(setq syntax-ppss-last (cons pos new-ppss))))
new-ppss)
(defun pm--set-transient-map (commands)
"Set transient map with COMMANDS.
COMMANDS is a list of commands which are bound to their
accessible keys as well as the basic event of those keys. Used
for \"cycling\" commands."
(let ((map (make-sparse-keymap)))
(mapc (lambda (cmd)
(mapc (lambda (vec)
(define-key map vec cmd)
(let ((basic-ev (elt vec (1- (length vec)))))
(define-key map (vector basic-ev) cmd)))
(where-is-internal cmd)))
commands)
(set-transient-map map)))
(defun pm--display-file (ofile)
(when ofile
(condition-case-unless-debug err
(let ((buff (get-file-buffer ofile)))
(when buff
(with-current-buffer buff
(with-demoted-errors "Error while reverting: %s"
(revert-buffer t t))))
(when (if (functionp polymode-display-output-file)
(funcall polymode-display-output-file ofile)
polymode-display-output-file)
(if (string-match-p "html\\|htm$" ofile)
(browse-url ofile)
(display-buffer (find-file-noselect ofile 'nowarn)))))
(error (message "Error while displaying '%s': %s"
(file-name-nondirectory ofile)
(error-message-string err))))))
(defun pm--symbol-name (str-or-symbol)
(if (symbolp str-or-symbol)
(symbol-name str-or-symbol)
str-or-symbol))
(defun pm--true-mode-symbol (mode)
"Resolve aliases of MODE and return the true MODE name."
(while (and mode (symbolp (symbol-function mode)))
(setq mode (symbol-function mode)))
mode)
(defun pm--get-existing-mode (mode fallback)
"Return MODE symbol if it's defined and is a valid function.
If so, return it, otherwise check in turn
`polymode-default-inner-mode', the FALLBACK and ultimately
`poly-fallback-mode'."
(pm--true-mode-symbol
(cond ((fboundp mode) mode)
((eq polymode-default-inner-mode 'host) (buffer-local-value 'major-mode (pm-base-buffer)))
((fboundp polymode-default-inner-mode) polymode-default-inner-mode)
((eq fallback 'host) (buffer-local-value 'major-mode (pm-base-buffer)))
((fboundp fallback) fallback)
(t 'poly-fallback-mode))))
(defun pm--get-innermode-mode (chunkmode type)
"Retrieve the mode name of for inner CHUNKMODE for span of TYPE."
(pm--get-existing-mode
(cl-case (pm-true-span-type chunkmode type)
(body (eieio-oref chunkmode 'mode))
(head (eieio-oref chunkmode 'head-mode))
(tail (eieio-oref chunkmode 'tail-mode))
(t (error "Invalid type (%s); must be one of body, head tail" type)))
(eieio-oref chunkmode 'fallback-mode)))
(defun pm-get-mode-symbol-from-name (name &optional fallback)
"Guess and return mode function from short NAME.
Return FALLBACK if non-nil, otherwise the value of
`polymode-default-inner-mode' if non-nil, otherwise value of slot
:fallback-mode which globally defaults to `poly-fallback-mode'."
(pm--true-mode-symbol
(cond
((or (null name)
(and (stringp name) (= (length name) 0)))
(or
(when (or (eq polymode-default-inner-mode 'host)
(fboundp polymode-default-inner-mode))
polymode-default-inner-mode)
(when (or (eq fallback 'host)
(fboundp fallback))
fallback)
'poly-fallback-mode))
((and (symbolp name) (fboundp name) name))
((let* ((str (pm--symbol-name
(or (cdr (assq (intern (pm--symbol-name name))
polymode-mode-name-aliases))
name)))
(mname (if (string-match-p "-mode$" str)
str
(concat str "-mode"))))
(or
(let ((mode (intern mname)))
(when (fboundp mode)
mode))
(let ((mode (intern (downcase mname))))
(when (fboundp mode)
mode))
(let ((dummy-file (concat "a." str)))
(cl-loop for (k . v) in auto-mode-alist
if (and (string-match-p k dummy-file)
(not (string-match-p "^poly-" (symbol-name v))))
return v))
(when (or (eq polymode-default-inner-mode 'host)
(fboundp polymode-default-inner-mode))
polymode-default-inner-mode)
(when (or (eq fallback 'host)
(fboundp fallback))
fallback)
'poly-fallback-mode))))))
(defun pm--oref-with-parents (object slot)
"Merge slots SLOT from the OBJECT and all its parent instances."
(let (VALS)
(while object
(setq VALS (append (and (slot-boundp object slot) (eieio-oref object slot))
VALS)
object (and (slot-boundp object :parent-instance)
(eieio-oref object 'parent-instance))))
VALS))
(defun pm--abrev-names (abrev-regexp list)
"Abbreviate names in LIST by erasing ABREV-REGEXP matches.
Elements of LIST can be either strings or symbols."
(mapcar (lambda (nm)
(let* ((str-nm (if (symbolp nm)
(symbol-name nm)
nm))
(prefix (replace-regexp-in-string "^poly-[^-]+\\(.+\\)" "" str-nm nil nil 1))
(is-lib (or (string= prefix "poly-r") (featurep (intern prefix)))))
(cons (replace-regexp-in-string abrev-regexp ""
(if is-lib
(replace-regexp-in-string "^poly-[^-]+-" "" str-nm)
str-nm))
str-nm)))
list))
(defun pm--object-value (obj)
(cond
((functionp obj)
(funcall obj))
((symbolp obj)
(symbol-value obj))
(t obj)))
(defun pm--oref-value (object slot)
(pm--object-value (eieio-oref object slot)))
(defun pm--prop-put (key val &optional object)
(oset (or object pm/polymode) -props
(plist-put (oref (or object pm/polymode) -props) key val)))
(defun pm--prop-get (key &optional object)
(plist-get (oref (or object pm/polymode) -props) key))
(defun pm--comment-region (beg end)
(when (> end 1)
(with-silent-modifications
(let ((beg (or beg (region-beginning)))
(end (or end (region-end))))
(let ((ch-beg (char-after beg))
(ch-end (char-before end)))
(add-text-properties beg (1+ beg)
(list 'syntax-table (cons 11 ch-beg)
'rear-nonsticky t
'polymode-comment 'start))
(add-text-properties (1- end) end
(list 'syntax-table (cons 12 ch-end)
'rear-nonsticky t
'polymode-comment 'end)))))))
(defun pm--uncomment-region (beg end)
(when (> end 1)
(with-silent-modifications
(let ((props '(syntax-table nil rear-nonsticky nil polymode-comment nil)))
(remove-text-properties (max beg (point-min)) (min end (point-max)) props)
))))
(defun pm--synchronize-points (&optional buffer)
"Synchronize the point in polymode buffers with the point in BUFFER."
(setq buffer (current-buffer))
(when (and polymode-mode
(buffer-live-p buffer))
(let* ((bufs (eieio-oref pm/polymode '-buffers))
(pos (with-current-buffer buffer (point))))
(dolist (b bufs)
(when (buffer-live-p b)
(with-current-buffer b
(goto-char pos)))))))
(defun pm--completing-read (prompt collection &optional predicate require-match
initial-input hist def inherit-input-method)
(if (and (listp collection)
(listp (car collection)))
(let* ((candidates (mapcar #'car collection))
(thirst (and hist
(delq nil (mapcar (lambda (x) (car (member x candidates)))
(symbol-value hist)))))
(def (or def (car thirst) (car candidates))))
(assoc (completing-read prompt candidates predicate t initial-input hist def inherit-input-method)
collection))
(completing-read prompt collection predicate require-match initial-input hist def inherit-input-method)))
(defvar polymode-exporter-output-file-format)
(defvar polymode-weaver-output-file-format)
(declare-function pm-export "polymode-export")
(declare-function pm-weave "polymode-weave")
(declare-function comint-exec "comint")
(declare-function comint-mode "comint")
(defun pm--wrap-callback (processor slot _ifile)
(let ((sentinel1 (eieio-oref processor slot))
(cur-dir default-directory)
(exporter (symbol-value (eieio-oref pm/polymode 'exporter)))
(obuffer (current-buffer)))
(if pm--export-spec
(let ((espec pm--export-spec))
(lambda (&rest args)
(with-current-buffer obuffer
(let ((wfile (apply sentinel1 args))
(pm--export-spec nil)
(pm--input-not-real t))
(when wfile
(when (listp wfile)
(setq wfile (car wfile)))
(pm-export exporter (car espec) (cdr espec) wfile))))))
(lambda (&rest args)
(with-current-buffer obuffer
(let ((ofile (apply sentinel1 args)))
(when ofile
(let ((ofiles (if (listp ofile) ofile (list ofile))))
(dolist (f ofiles)
(pm--display-file (expand-file-name f cur-dir)))))))))))
(defun pm--file-mod-time (file)
(and (stringp file)
(file-exists-p file)
(nth 5 (file-attributes file))))
(defvar-local pm--process-buffer nil)
(defun pm--run-shell-command (command sentinel buff-name message)
(require 'comint)
(let* ((buffer (get-buffer-create buff-name))
(process nil)
(dd default-directory)
(inhibit-read-only t))
(with-current-buffer buffer
(setq-local default-directory dd)
(setq buffer-read-only nil)
(erase-buffer)
(insert message)
(comint-exec buffer buff-name shell-file-name nil
(list shell-command-switch command))
(setq process (get-buffer-process buffer))
(comint-mode)
(goto-address-mode 1)
(set-process-sentinel process sentinel)
(setq pm--process-buffer t)
(set-marker (process-mark process) (point-max))
(process-put process :output-file pm--output-file)
(process-put process :output-file-mod-time (pm--file-mod-time pm--output-file))
(process-put process :input-file pm--input-file)
(when polymode-display-process-buffers
(display-buffer buffer `(nil . ((inhibit-same-window . ,pop-up-windows)))))
nil)))
(defun pm--make-shell-command-sentinel (action)
(lambda (process _name)
"Sentinel built with `pm--make-shell-command-sentinel'."
(let ((buff (process-buffer process))
(status (process-exit-status process)))
(if (> status 0)
(progn
(message "Errors during %s; process exit status %d" action status)
(ding) (sit-for 1)
nil)
(with-current-buffer buff
(let ((ofile (process-get process :output-file)))
(cond
((functionp ofile) (funcall ofile))
(ofile
(let ((otime (process-get process :output-file-mod-time))
(ntime (pm--file-mod-time ofile)))
(if (or (null ntime)
(and otime
(not (time-less-p otime ntime))))
(progn
(display-buffer (current-buffer))
(message "Output file unchanged. Either input unchanged or errors during %s." action)
(ding) (sit-for 1)
ofile)
(message "Done with %s" action)
ofile)))
(t (display-buffer (current-buffer)) nil))))))))
(fset 'pm-default-shell-export-sentinel (pm--make-shell-command-sentinel "export"))
(fset 'pm-default-shell-weave-sentinel (pm--make-shell-command-sentinel "weaving"))
(defun pm--make-selector (specs elements)
(cond ((functionp elements) elements)
((listp elements)
(let ((spec-alist (cl-mapcar #'cons specs elements)))
(lambda (selsym &rest _ignore)
(cdr (assoc selsym spec-alist)))))
(t (error "Elements argument must be either a list or a function"))))
(defun pm--selector (processor type id)
(let ((spec (or (assoc id (eieio-oref processor type))
(error "%s spec '%s' cannot be found in '%s'"
(symbol-name type) id (eieio-object-name processor))))
(names (cond
((eq type :from) '(regexp doc command))
((eq type :to) '(ext doc t-spec))
((eq type :from-to) '(regexp ext doc command))
(t (error "Invalid type '%s'" type)))))
(cons id (pm--make-selector names (cdr spec)))))
(defun pm--selector-match (el &optional file)
(let* ((id (car el))
(regexp (funcall (cdr el) 'regexp id)))
(or (funcall (cdr el) 'match id file)
(and regexp
(string-match-p regexp (or file buffer-file-name))))))
(defun pm--matched-selectors (translator slot)
(let ((translator (if (symbolp translator)
(symbol-value translator)
translator)))
(cl-loop for el in (pm--selectors translator slot)
when (pm--selector-match el)
collect el)))
(defun pm--selectors (processor type)
(let ((ids (mapcar #'car (eieio-oref processor type))))
(mapcar (lambda (id) (pm--selector processor type id)) ids)))
(defun pm--output-command.file (output-file-format sfrom &optional sto quote)
(cl-flet ((squote (arg) (or (and (stringp arg)
(if quote (shell-quote-argument arg) arg))
"")))
(let* ((el (or sto sfrom))
(base-ofile (or (funcall (cdr el) 'output-file (car el))
(let ((ext (funcall (cdr el) 'ext (car el))))
(when ext
(concat (format output-file-format
(file-name-base buffer-file-name))
"." ext)))))
(ofile (and (stringp base-ofile)
(expand-file-name base-ofile)))
(oname (and (stringp base-ofile)
(file-name-base base-ofile)))
(t-spec (and sto (funcall (cdr sto) 't-spec (car sto))))
(command-w-formats (or (and sto (funcall (cdr sto) 'command (car sto)))
(and (listp t-spec) (car t-spec))
(funcall (cdr sfrom) 'command (car sfrom))))
(command (format-spec command-w-formats
(list (cons ?i (squote (file-name-nondirectory buffer-file-name)))
(cons ?I (squote buffer-file-name))
(cons ?o (squote base-ofile))
(cons ?O (squote ofile))
(cons ?b (squote oname))
(cons ?t (squote t-spec))))))
(cons command (or ofile base-ofile)))))
(defun pm--process-internal (processor from to ifile &optional callback quote)
(let ((is-exporter (object-of-class-p processor 'pm-exporter)))
(if is-exporter
(unless (and from to)
(error "For exporter both FROM and TO must be supplied (from: %s, to: %s)" from to))
(unless from
(error "For weaver FROM must be supplied (from: %s)" from)))
(let* ((sfrom (if is-exporter
(pm--selector processor :from from)
(pm--selector processor :from-to from)))
(sto (and is-exporter (pm--selector processor :to to)))
(ifile (or ifile buffer-file-name))
(ibuffer (if pm--input-not-real
(find-file-noselect ifile t)
(or (get-file-buffer ifile)
(find-file-noselect ifile))))
(output-format (if is-exporter
polymode-exporter-output-file-format
polymode-weaver-output-file-format)))
(when (buffer-live-p ibuffer)
(with-current-buffer ibuffer
(save-buffer)
(let ((comm.ofile (pm--output-command.file output-format sfrom sto quote)))
(let* ((pm--output-file (cdr comm.ofile))
(pm--input-file ifile)
(omt (and polymode-skip-processing-when-unmodified
(stringp pm--output-file)
(pm--file-mod-time pm--output-file)))
(imt (and omt (pm--file-mod-time pm--input-file)))
(ofile (if (and imt (time-less-p imt omt))
(progn
(message "Not re-%s as input file '%s' hasn't changed"
(if is-exporter "exporting" "weaving")
(file-name-nondirectory ifile))
pm--output-file)
(message "%s '%s' with '%s' ..."
(if is-exporter "EXPORTING" "WEAVING")
(file-name-nondirectory ifile)
(eieio-object-name processor))
(let ((fn (with-no-warnings
(eieio-oref processor 'function)))
(args (delq nil (list from to)))
(comm (car comm.ofile)))
(if callback
(progn (apply fn comm callback args)
nil)
(apply fn comm args))))))
(when ofile
(if pm--export-spec
(let ((pm--input-not-real t)
(espec pm--export-spec)
(pm--export-spec nil))
(when (listp ofile)
(setq ofile (car ofile)))
(pm-export (symbol-value (eieio-oref pm/polymode 'exporter))
(car espec) (cdr espec)
ofile))
(pm--display-file ofile))))))))))
(provide 'polymode-core)