forked from polymode/polymode
-
Notifications
You must be signed in to change notification settings - Fork 0
/
polymode-methods.el
714 lines (624 loc) · 28.2 KB
/
polymode-methods.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
(require 'polymode-common)
;;; INITIALIZATION
(defgeneric pm-initialize (config)
"Initialize current buffer with CONFIG.
First initialize the -hostmode and -innermodes slots of polymode
object ...")
(defmethod pm-initialize ((config pm-polymode))
;; fixme: reinstalation leads to infloop of pm--fontify-region-original and others ...
;; On startup with local auto vars emacs reinstals the mode twice .. waf?
;; Temporary fix: don't install twice
(unless pm/polymode
(let* ((chunkmode (clone (symbol-value (oref config :hostmode))))
(_ (oset chunkmode -buffer (current-buffer)))
;; set if nil, to allow unspecified host chunkmodes to be used in minor modes
(host-mode (or (oref chunkmode :mode)
(oset chunkmode :mode major-mode))))
(unless (or (eq major-mode host-mode)
(eq polymode-major-mode host-mode))
(let ((polymode-mode t)) ;;major-modes might check it
(funcall host-mode)))
;; fixme: maybe: inconsistencies?
;; 1) not calling pm-install-buffer on host-buffer
;; But, we are not creating/installing a new buffer here .. so it is a
;; different thing .. and is probably ok
;; 2) not calling config's :minor-mode (polymode function).
;; But polymode function calls pm-initialize... so I guess it is ok
(oset config -hostmode chunkmode)
(setq pm/polymode config)
(setq pm/chunkmode chunkmode)
(setq pm/type 'host)
(add-hook 'flyspell-incorrect-hook 'pm--flyspel-dont-highlight-in-chunkmodes nil t)
(prog1 (pm--setup-buffer) ; general setup for host and innermode buffers
(let ((PI pm/polymode) IFs)
;; aggregate and run hooks; parents first
(while PI
(setq IFs (append (and (slot-boundp PI :init-functions) ; don't cascade
(oref PI :init-functions))
IFs)
PI (and (slot-boundp PI :parent-instance)
(oref PI :parent-instance))))
(run-hooks 'IFs))))))
(defmethod pm-initialize ((config pm-polymode-one))
(call-next-method)
(eval `(oset config -innermodes
(list (clone ,(oref config :innermode))))))
(defmethod pm-initialize ((config pm-polymode-multi))
(call-next-method)
(oset config -innermodes
(mapcar (lambda (sub-name)
(clone (symbol-value sub-name)))
(oref config :innermodes))))
;;; BUFFERS
(defgeneric pm-get-buffer (chunkmode &optional span-type)
"Get the indirect buffer associated with SUBMODE and
SPAN-TYPE. Should return nil if buffer has not yet been
installed. Also see `pm-get-span'.")
(defmethod pm-get-buffer ((chunkmode pm-chunkmode) &optional type)
(oref chunkmode -buffer))
(defmethod pm-get-buffer ((chunkmode pm-hbtchunkmode) &optional type)
(cond ((eq 'body type) (oref chunkmode -buffer))
((eq 'head type) (oref chunkmode -head-buffer))
((eq 'tail type) (oref chunkmode -tail-buffer))
(t (error "Don't know how to select buffer of type '%s' for chunkmode '%s' of class '%s'"
type (pm--object-name chunkmode) (class-of chunkmode)))))
(defgeneric pm-select-buffer (chunkmode span)
"Ask SUBMODE to select (make current) its indirect buffer
corresponding to the type of the SPAN returned by
`pm-get-span'.")
(defmethod pm-select-buffer ((chunkmode pm-chunkmode) span)
"Select the buffer associated with SUBMODE.
Install a new indirect buffer if it is not already installed. For
this method to work correctly, SUBMODE's class should define
`pm-install-buffer' and `pm-get-buffer' methods."
(let* ((type (car span))
(buff (pm-get-buffer chunkmode type)))
(unless (buffer-live-p buff)
(pm-install-buffer chunkmode type)
(setq buff (pm-get-buffer chunkmode type)))
(pm--select-buffer buff)))
(defmethod pm-select-buffer ((chunkmode pm-hbtchunkmode) span)
(call-next-method)
(pm--transfer-vars-from-base))
(defmethod pm-select-buffer ((config pm-polymode-multi-auto) &optional span)
;; :fixme: pm-get-span on multi configs returns config as last object of
;; span. That's freaking confusing.
(if (null (car span))
(pm-select-buffer (oref config -hostmode) span)
(let ((type (car span))
(proto (symbol-value (oref config :auto-innermode)))
chunkmode)
(save-excursion
(goto-char (cadr span))
(unless (eq type 'head)
(re-search-backward (oref proto :head-reg) nil 'noerr))
(let* ((str (or (and (oref proto :retriever-regexp)
(re-search-forward (oref proto :retriever-regexp))
(match-string-no-properties (oref proto :retriever-num)))
(and (oref proto :retriever-function)
(funcall (oref proto :retriever-function)))
(error "retriever subexpression didn't match")))
(name (concat "auto-innermode:" str)))
(setq chunkmode
(or (loop for obj in (oref config -auto-innermodes)
when (equal name (object-name-string obj))
return obj)
(let ((new-obj (clone proto name
:mode (pm--get-mode-symbol-from-name str))))
(object-add-to-list config '-auto-innermodes new-obj)
new-obj)))))
(pm-select-buffer chunkmode span))))
(defgeneric pm-install-buffer (chunkmode &optional type)
"Ask SUBMODE to install an indirect buffer corresponding to
span TYPE. Should return newly installed/retrieved buffer.")
(defmethod pm-install-buffer ((chunkmode pm-chunkmode) &optional type)
"Independently on the TYPE call `pm/create-indirect-buffer'
create and install a new buffer in slot -buffer of SUBMODE."
(oset chunkmode -buffer
(pm--create-chunkmode-buffer-maybe chunkmode type)))
(defmethod pm-install-buffer ((chunkmode pm-hbtchunkmode) type)
"Depending of the TYPE install an indirect buffer into
slot -buffer of SUBMODE. Create this buffer if does not exist."
(pm--set-chunkmode-buffer chunkmode type
(pm--create-chunkmode-buffer-maybe chunkmode type)))
(defun pm--get-adjusted-background (prop)
;; if > lighten on dark backgroun. Oposite on light.
(color-lighten-name (face-background 'default)
(if (eq (frame-parameter nil 'background-mode) 'light)
(- prop) ;; darken
prop)))
(defun pm--adjust-chunk-face (beg end face)
;; propertize 'face of the region by adding chunk specific configuration
(interactive "r")
(when face
(with-current-buffer (current-buffer)
(let ((face (or (and (numberp face)
(list (cons 'background-color
(pm--get-adjusted-background face))))
face))
(pchange nil))
;; (while (not (eq pchange end))
;; (setq pchange (next-single-property-change beg 'face nil end))
;; (put-text-property beg pchange 'face
;; `(,face ,@(get-text-property beg 'face)))
;; (setq beg pchange))
(font-lock-prepend-text-property beg end 'face face)))))
(defun pm--adjust-visual-line-mode (vlm)
(when (not (eq visual-line-mode vlm))
(if (null vlm)
(visual-line-mode -1)
(visual-line-mode 1))))
;; move only in post-command hook, after buffer selection
(defvar pm--can-move-overlays nil)
(defun pm--move-overlays-to (new-buff)
(when pm--can-move-overlays
(mapc (lambda (o)
(move-overlay o (overlay-start o) (overlay-end o) new-buff))
(overlays-in 1 (1+ (buffer-size))))))
(defun pm--transfer-vars-from-base ()
(let ((bb (pm/base-buffer)))
(dolist (var '(buffer-file-name))
(set var (buffer-local-value var bb)))))
(defun pm--select-buffer (buffer)
(when (and (not (eq buffer (current-buffer)))
(buffer-live-p buffer))
(let ((point (point))
(window-start (window-start))
(visible (pos-visible-in-window-p))
(oldbuf (current-buffer))
(vlm visual-line-mode)
(ractive (region-active-p))
(mkt (mark t))
(bis buffer-invisibility-spec))
(pm--move-overlays-to buffer)
(switch-to-buffer buffer)
(setq buffer-invisibility-spec bis)
(pm--adjust-visual-line-mode vlm)
(bury-buffer oldbuf)
;; fixme: wha tis the right way to do this ... activate-mark-hook?
(if (not ractive)
(deactivate-mark)
(set-mark mkt)
(activate-mark))
(goto-char point)
;; Avoid the display jumping around.
(when visible
(set-window-start (get-buffer-window buffer t) window-start)))))
(defun pm--setup-buffer (&optional buffer)
;; General buffer setup, should work for indirect and base buffers
;; alike. Assume pm/polymode and pm/chunkmode is already in place. Return
;; the buffer.
(let ((buff (or buffer (current-buffer))))
(with-current-buffer buff
;; Don't let parse-partial-sexp get fooled by syntax outside
;; the chunk being fontified.
;; font-lock, forward-sexp etc should see syntactic comments
;; (set (make-local-variable 'parse-sexp-lookup-properties) t)
(set (make-local-variable 'font-lock-dont-widen) t)
(when pm--dbg-fontlock
(setq pm--fontify-region-original
font-lock-fontify-region-function)
(set (make-local-variable 'font-lock-fontify-region-function)
#'pm/fontify-region)
(setq pm--syntax-begin-function-original
syntax-begin-function)
(set (make-local-variable 'syntax-begin-function)
#'pm/syntax-begin-function))
(set (make-local-variable 'polymode-mode) t)
;; Indentation should first narrow to the chunk. Modes
;; should normally just bind `indent-line-function' to
;; handle indentation.
(when (and indent-line-function ; not that it should ever be nil...
(oref pm/chunkmode :protect-indent-line))
(setq pm--indent-line-function-original indent-line-function)
(set (make-local-variable 'indent-line-function) 'pm-indent-line-dispatcher))
(add-hook 'kill-buffer-hook 'pm--kill-indirect-buffer t t)
(when pm--dbg-hook
(add-hook 'post-command-hook 'polymode-select-buffer nil t))
(object-add-to-list pm/polymode '-buffers (current-buffer)))
buff))
(defvar-local pm--killed-once nil)
(defun pm--kill-indirect-buffer ()
;; find-alternate-file breaks (https://github.com/vspinu/polymode/issues/79)
(let ((base (buffer-base-buffer)))
(when (and base (buffer-live-p base))
;; 'base' is non-nil in indirect buffers only
(set-buffer-modified-p nil)
(unless (buffer-local-value 'pm--killed-once base)
(with-current-buffer base
(setq pm--killed-once t))
(kill-buffer base)))))
(defvar pm--ib-prefix "")
(defun pm--create-indirect-buffer (mode)
"Create indirect buffer with major MODE and initialize appropriately.
This is a low lever function which must be called, one way or
another from `pm/install' method. Among other things store
`pm/polymode' from the base buffer (must always exist!) in
the newly created buffer.
Return newlly created buffer."
(unless (buffer-local-value 'pm/polymode (pm/base-buffer))
(error "`pm/polymode' not found in the base buffer %s" (pm/base-buffer)))
(setq mode (pm--get-available-mode mode))
(with-current-buffer (pm/base-buffer)
(let* ((config (buffer-local-value 'pm/polymode (current-buffer)))
(new-name
(generate-new-buffer-name
(format "%s%s[%s]" pm--ib-prefix (buffer-name)
(replace-regexp-in-string "-mode" "" (symbol-name mode)))))
(new-buffer (make-indirect-buffer (current-buffer) new-name))
;; (hook pm/indirect-buffer-hook)
(file (buffer-file-name))
(base-name (buffer-name))
(jit-lock-mode nil)
(coding buffer-file-coding-system))
(with-current-buffer new-buffer
(let ((polymode-mode t)) ;;major-modes might check it
(funcall mode))
;; hopefully temporary hack:
(pm--activate-jit-lock-mode-maybe)
(setq polymode-major-mode mode)
;; Avoid the uniqified name for the indirect buffer in the mode line.
(when pm--dbg-mode-line
(setq mode-line-buffer-identification
(propertized-buffer-identification base-name)))
(setq pm/polymode config)
(setq buffer-file-coding-system coding)
(setq buffer-file-name file)
(vc-find-file-hook))
new-buffer)))
;;; SPAN MANIPULATION
(defgeneric pm-get-span (chunkmode &optional pos)
"Ask the CHUNKMODE for the span at point.
Return a list of three elements (TYPE BEG END OBJECT) where TYPE
is a symbol representing the type of the span surrounding
POS (head, tail, body). BEG and END are the coordinates of the
span. OBJECT is a sutable object which is 'responsable' for this
span. This is an object that could be dispached upon with
`pm-select-buffer', .. (fixme: complete this list).
Should return nil if there is no SUBMODE specific span around POS.")
(defmethod pm-get-span (chunkmode &optional pos)
"Simply return nil. Base mode usually do not compute the span."
nil)
(defmethod pm-get-span ((config pm-polymode) &optional pos)
"Apply pm-get-span on every element of chunkmodes slot of config object.
Return a cons (chunkmode . span), for which START is closest to
POS (and before it); i.e. the innermost span. POS defaults to
point."
(save-restriction
(widen)
;; fixme: host should be last, to take advantage of the chunkmodes computation
(let* ((smodes (cons (oref config -hostmode)
(oref config -innermodes)))
(start (point-min))
(end (point-max))
(pos (or pos (point)))
(span (list nil start end nil))
val)
(dolist (sm smodes)
(setq val (pm-get-span sm pos))
(when (and val
(or (> (nth 1 val) start)
(< (nth 2 val) end)))
(if (or (car val)
(null span))
(setq span val
start (nth 1 val)
end (nth 2 val))
;; nil car means outer chunkmode (usually host). And it can be an
;; intersection of spans returned by 2 different neighbour inner
;; chunkmodes. See rapport mode for an example
(setq start (max (nth 1 val)
(nth 1 span))
end (min (nth 2 val)
(nth 2 span)))
(setcar (cdr span) start)
(setcar (cddr span) end))))
(unless (and (<= start end) (<= pos end) (>= pos start))
(error "Bad polymode selection: span:%s pos:%s"
(list start end) pos))
(when (null (car span)) ; chunkmodes can compute the host span by returning nil
(setcar (last span) (oref config -hostmode)))
span)))
;; No need for this one so far. Basic method iterates through -innermodes
;; anyhow.
;; (defmethod pm-get-span ((config pm-polymode-multi) &optional pos))
(defmethod pm-get-span ((config pm-polymode-multi-auto) &optional pos)
(let ((span-other (call-next-method))
(proto (symbol-value (oref config :auto-innermode))))
(if (oref proto :head-reg)
(let ((span (pm--span-at-point (oref proto :head-reg)
(oref proto :tail-reg)
pos)))
(if (and span-other
(or (> (nth 1 span-other) (nth 1 span))
(< (nth 2 span-other) (nth 2 span))))
;; treat intersections with the host mode
(if (car span-other)
span-other ;not host
;; here, car span should better be nil; no explicit check
(setcar (cdr span-other) (max (nth 1 span-other) (nth 1 span)))
(setcar (cddr span-other) (min (nth 2 span-other) (nth 2 span)))
span-other)
(append span (list config)))) ;fixme: this returns config as last object
span-other)))
(defmethod pm-get-span ((chunkmode pm-hbtchunkmode) &optional pos)
"Return a list of the form (TYPE POS-START POS-END SELF).
TYPE can be 'body, 'head or 'tail. SELF is just a chunkmode object
in this case."
(with-slots (head-reg tail-reg head-mode tail-mode) chunkmode
(let* ((span (pm--span-at-point head-reg tail-reg pos))
(type (car span)))
(when (or (and (eq type 'head) (eq head-mode 'host))
(and (eq type 'tail) (or (eq tail-mode 'host)
(and (null tail-mode)
(eq head-mode 'host)))))
(setcar span nil))
(append span (list chunkmode)))))
(defmacro pm-create-indented-block-matchers (name regex)
"Defines 2 functions, each return a list of the start and end points of the
HEAD and TAIL portions of an indented block of interest, via some regex.
You can then use these functions in the defcustom pm-inner modes.
e.g.
(pm-create-indented-block-matchers 'slim-coffee' \"^[^ ]*\\(.*:? *coffee: *\\)$\")
creates the functions
pm-slim-coffee-head-matcher
pm-slim-coffee-tail-matcher
In the example below,
The head matcher will match against 'coffee:', returning the positions of the
start and end of 'coffee:'
The tail matcher will return a list (n, n) of the final characters is the block.
|<----- Uses this indentation to define the left edge of the 'block'
|
|<--->| This region is higlighted by the :head-mode in the block-matchers
| |
| |<----- the head matcher uses this column as the end of the head
| |
----:-----:-------------- example file -----------------------------------------
1| : :
2| coffee:
3| myCoffeeCode()
4| moreCode ->
5| do things
6| :
7| This is no longer in the block
8| :
----------------:---------------------------------------------------------------
--->|<----- this region of 0 width is highlighted by the :tail-mode
the 'block' ends after this column on line 5
All the stuff after the -end- of the head and before the start of the tail is
sent to the new mode for syntax highlighting
"
(let* ((head-name (intern (format "pm-%s-head-matcher" name)))
(tail-name (intern (format "pm-%s-tail-matcher" name))))
`(progn
(defun ,head-name (ahead)
(when (re-search-forward ,regex nil t ahead)
(cons (match-beginning 1) (match-end 1))))
(defun ,tail-name (ahead)
(save-excursion
;; (cons (point-max) (point-max)))))))
(goto-char (car (,head-name 1)))
(let* ((block-col (current-indentation))
(posn (catch 'break
(while (not (eobp))
(forward-line 1)
(when (and (<= (current-indentation) block-col)
(not (progn
(beginning-of-line)
(looking-at "^[[:space:]]*$"))))
(throw 'break (point-at-bol))))
(throw 'break (point-max)))))
(cons posn posn)))))))
(defun pm--default-matcher (reg ahead)
(if (< ahead 0)
(if (re-search-backward reg nil t)
(cons (match-beginning 0) (match-end 0)))
(if (re-search-forward reg nil t)
(cons (match-beginning 0) (match-end 0)))))
;; fixme: there should be a simpler way... check the code and document
(defun pm--span-at-point-fun-fun (hd-matcher tl-matcher)
(save-excursion
(let ((pos (point))
(posh (funcall hd-matcher -1)))
(if (null posh)
;; special first chunk
(let ((posh1 (progn (goto-char (point-min))
(funcall hd-matcher 1))))
(if (and posh1
(<= (car posh1) pos)
(< pos (cdr posh1)))
(list 'head (car posh1) (cdr posh1))
(list nil (point-min) (or (car posh1)
(point-max)))))
(let ((post (progn (goto-char (car posh))
(or (funcall tl-matcher 1)
(cons (point-max) (point-max))))))
(if (and (<= (cdr posh) pos)
(< pos (car post)))
(list 'body (cdr posh) (car post))
(if (and (<= (car post) pos)
(< pos (cdr post)))
(list 'tail (car post) (cdr post))
(if (< pos (cdr post))
;; might be in the head
(progn
(goto-char (car post))
(let ((posh1 (funcall hd-matcher -1)))
(if (and (<= (car posh1) pos)
(< pos (cdr posh1)))
(list 'head (car posh1) (cdr posh1))
(list nil (cdr posh) (car posh1))))) ;; posh is point min, fixme: not true anymore?
(goto-char (cdr post))
(let ((posh1 (or (funcall hd-matcher 1)
(cons (point-max) (point-max)))))
(if (and posh
(<= (car posh1) pos )
(< pos (cdr posh1)))
(list 'head (car posh1) (cdr posh1))
(list nil (cdr post) (car posh1))))))))))))
(defun pm--span-at-point-reg-reg (head-matcher tail-matcher)
;; Guaranteed to produce non-0 length spans. If no span has been found
;; (head-matcher didn't match) return (nil (point-min) (point-max)).
;; xxx1 relate to the first ascending search
;; xxx2 relate to the second descending search
(save-excursion
(let* ((pos (point))
(head1-beg (and (re-search-backward head-matcher nil t)
(match-beginning 0)))
(head1-end (and head1-beg (match-end 0))))
(if head1-end
;; we know that (>= pos head1-end)
;; -----------------------
;; host](head)[body](tail)[host](head)
(let* ((tail1-beg (and (goto-char head1-end)
(re-search-forward tail-matcher nil t)
(match-beginning 0)))
(tail1-end (and tail1-beg (match-end 0)))
(tail1-beg (or tail1-beg (point-max)))
(tail1-end (or tail1-end (point-max))))
(if (or (< pos tail1-end)
(= tail1-end (point-max)))
(if (<= pos tail1-beg)
;; ------
;; host](head)[body](tail)[host](head))
(list 'body head1-end tail1-beg)
;; -----
;; host](head](body](tail)[host](head)
(list 'tail tail1-beg tail1-end))
;; ------------
;; host](head](body](tail)[host](head)
(let* ((head2-beg (or (and (re-search-forward head-matcher nil t)
(match-beginning 0))
(point-max))))
(if (<= pos head2-beg)
;; ------
;; host](head](body](tail)[host](head)
(list nil tail1-end head2-beg)
;; ------
;; host](head](body](tail)[host](head)
(list 'head head2-beg (match-end 0))))))
;; -----------
;; host](head)[body](tail)[host
(let ((head2-beg (and (goto-char (point-min))
(re-search-forward head-matcher nil t)
(match-beginning 0))))
(if (null head2-beg)
;; no span found
(list nil (point-min) (point-max))
(if (<= pos head2-beg)
;; -----
;; host](head)[body](tail)[host
(list nil (point-min) head2-beg)
;; ------
;; host](head)[body](tail)[host
(list 'head head2-beg (match-end 0)))))))))
(defun pm--span-at-point (head-matcher tail-matcher &optional pos)
"Basic span detector with head/tail.
Either of HEAD-MATCHER and TAIL-MATCHER can be a regexp or a
function. When a function the matcher must accept one argument
that can take either values 1 (forwards search) or -1 (backward
search). This function must return either nil (no match) or
a (cons BEG END) representing the span of the head or tail
respectively. See `pm--default-matcher' for an example.
Return (type span-start span-end) where type is one of the
follwoing symbols:
nil - pos is between point-min and head-reg, or between tail-reg and point-max
body - pos is between head-reg and tail-reg (exclusively)
head - head span
tail - tail span"
;; ! start of the span is part of the span !
(save-restriction
(widen)
(goto-char (or pos (point)))
(cond ((and (stringp head-matcher)
(stringp tail-matcher))
(pm--span-at-point-reg-reg head-matcher tail-matcher))
((and (stringp head-matcher)
(functionp tail-matcher))
(pm--span-at-point-fun-fun
(lambda (ahead) (pm--default-matcher head-matcher ahead))
tail-matcher))
((and (functionp head-matcher)
(stringp tail-matcher))
(pm--span-at-point-fun-fun
head-matcher
(lambda (ahead) (pm--default-matcher tail-matcher ahead))))
((and (functionp head-matcher)
(functionp tail-matcher))
(pm--span-at-point-fun-fun head-matcher tail-matcher))
(t (error "head and tail matchers should be either regexp strings or functions")))))
;;; INDENT
(defun pm-indent-line-dispatcher ()
"Dispatch methods indent methods on current span."
(let ((span (pm/get-innermost-span)))
(pm-indent-line (car (last span)) span)))
(defgeneric pm-indent-line (&optional chunkmode span)
"Indent current line.
Protect and call original indentation function associated with
the chunkmode.")
(defun pm--indent-line (span)
;; istr is auto-indent string
(unwind-protect
(save-restriction
(pm--comment-region 1 (nth 1 span))
(pm/narrow-to-span span)
(funcall pm--indent-line-function-original))
(pm--uncomment-region 1 (nth 1 span))))
(defmethod pm-indent-line ((chunkmode pm-chunkmode) &optional span)
(pm--indent-line span))
(defmethod pm-indent-line ((chunkmode pm-hbtchunkmode) &optional span)
"Indent line in inner chunkmodes.
When point is at the beginning of head or tail, use parent chunk
to indent."
;; sloppy work:
;; Assumes multiline chunks and single-line head/tail.
;; Assumes current buffer is the correct buffer.
(let ((pos (point))
shift delta)
(cond ((or (eq 'head (car span))
(eq 'tail (car span)))
;; use parent's indentation function in head and tail
(back-to-indentation)
(setq delta (- pos (point)))
(backward-char)
(let ((parent-span (pm/get-innermost-span)))
(pm-select-buffer (car (last parent-span)) parent-span)
(forward-char)
(pm--indent-line parent-span)
(when (eq 'tail (car span))
(setq shift (pm--get-head-shift parent-span))
(indent-to (+ shift (- (point) (point-at-bol))))))
(if (> delta 0)
(goto-char (+ (point) delta))))
(t
(setq shift (pm--get-head-shift span))
(pm--indent-line span)
(when (= (current-column) 0)
(setq shift (+ shift (oref chunkmode :indent-offset))))
(setq delta (- (point) (point-at-bol)))
(beginning-of-line)
(indent-to shift)
(goto-char (+ (point) delta))))))
;; fixme: This one is nowhere used?
(defmethod pm-indent-line ((chunkmode pm-polymode-multi-auto) &optional span)
(pm-select-buffer chunkmode span)
(pm-indent-line pm/chunkmode span))
(defun pm--get-head-shift (span)
(save-excursion
(goto-char (cadr span))
(back-to-indentation)
(- (point) (point-at-bol))))
;;; FACES
(defgeneric pm-get-adjust-face (chunkmode &optional type))
(defmethod pm-get-adjust-face ((chunkmode pm-chunkmode) &optional type)
(oref chunkmode :adjust-face))
(defmethod pm-get-adjust-face ((chunkmode pm-hbtchunkmode) &optional type)
(setq type (or type pm/type))
(cond ((eq type 'head)
(oref chunkmode :head-adjust-face))
((eq type 'tail)
(if (eq 'head (oref pm/chunkmode :tail-adjust-face))
(oref pm/chunkmode :head-adjust-face)
(oref pm/chunkmode :tail-adjust-face)))
(t (oref pm/chunkmode :adjust-face))))
(provide 'polymode-methods)