1;;; yatexsec.el --- YaTeX sectioning browser
2;;;
3;;; (c) 1994-2017 by HIROSE Yuuji [yuuji@yatex.org]
4;;; Last modified Wed May 30 13:29:50 2018 on firestorm
5;;; $Id: yatexsec.el,v a58a35eac93f 2018-07-31 08:21 +0900 yuuji $
6
7;;; Code:
8(defvar YaTeX-sectioning-level
9  '(("part" . 0)
10    ("chapter" . 1)
11    ("section" . 2)
12    ("subsection" . 3)
13    ("subsubsection" . 4)
14    ("paragraph" . 5)
15    ("subparagraph" . 6))
16  "*Alist of LaTeX's sectioning command and its level.
17This value must be written in numerically ascending order and consecutive.
18Needn't define the level of `*' commands such as `section*'.")
19
20(defvar YaTeX-sectioning-max-level
21  (cdr (nth (1- (length YaTeX-sectioning-level)) YaTeX-sectioning-level))
22  "*The heighest(numerically) level of sectioning command.
23This must be the heighest number in YaTeX-sectioning-level.")
24
25(defun YaTeX-sectioning-map-hide (map)
26  (let ((ch ?0))
27    (while (<= ch ?9)
28      (define-key map (char-to-string ch) 'YaTeX-sectioning-hide)
29      (setq ch (1+ ch))))
30)
31
32(defvar YaTeX-sectioning-minibuffer-map nil
33  "Key map used in minibuffer for sectioning.")
34(if YaTeX-sectioning-minibuffer-map nil
35  (setq YaTeX-sectioning-minibuffer-map
36	(copy-keymap minibuffer-local-completion-map))
37  (define-key YaTeX-sectioning-minibuffer-map "\C-p"
38    'YaTeX-sectioning-up)
39  (define-key YaTeX-sectioning-minibuffer-map "\C-e"
40    'YaTeX-sectioning-up)
41  (define-key YaTeX-sectioning-minibuffer-map "\C-i"
42    'YaTeX-minibuffer-complete)
43  (define-key YaTeX-sectioning-minibuffer-map " "
44    'YaTeX-minibuffer-complete)
45  (define-key YaTeX-sectioning-minibuffer-map "\C-n"
46    'YaTeX-sectioning-down)
47  (define-key YaTeX-sectioning-minibuffer-map "\C-x"
48    'YaTeX-sectioning-down)
49  (define-key YaTeX-sectioning-minibuffer-map "\C-v"
50    'YaTeX-sectioning-scroll-up)
51  (define-key YaTeX-sectioning-minibuffer-map "\C-c"
52    'YaTeX-sectioning-scroll-up)
53  (define-key YaTeX-sectioning-minibuffer-map "\M-v"
54    'YaTeX-sectioning-scroll-down)
55  (define-key YaTeX-sectioning-minibuffer-map "\C-r"
56    'YaTeX-sectioning-scroll-down)
57  (define-key YaTeX-sectioning-minibuffer-map "\C-w"
58    (function (lambda () (interactive) (YaTeX-sectioning-scroll-down 1))))
59  (define-key YaTeX-sectioning-minibuffer-map "\C-z"
60    (function (lambda () (interactive) (YaTeX-sectioning-scroll-up 1))))
61  (define-key YaTeX-sectioning-minibuffer-map "\C-l"
62    'YaTeX-sectioning-recenter)
63  (define-key YaTeX-sectioning-minibuffer-map "?"
64    'YaTeX-sectioning-help)
65  (YaTeX-sectioning-map-hide YaTeX-sectioning-minibuffer-map)
66)
67
68(defvar YaTeX-sectioning-buffer-map nil
69  "Key map used in YaTeX-sectioning-buffer.")
70(if YaTeX-sectioning-buffer-map nil
71  (setq YaTeX-sectioning-buffer-map (make-sparse-keymap))
72  (define-key YaTeX-sectioning-buffer-map " "	'YaTeX-sectioning-buffer-jump)
73  (define-key YaTeX-sectioning-buffer-map "."	'YaTeX-sectioning-buffer-show)
74  (define-key YaTeX-sectioning-buffer-map (concat YaTeX-prefix "\C-c")
75    'YaTeX-sectioning-buffer-jump)
76  (define-key YaTeX-sectioning-buffer-map "u"	'YaTeX-shift-section-up)
77  (define-key YaTeX-sectioning-buffer-map "d"	'YaTeX-shift-section-down)
78  (define-key YaTeX-sectioning-buffer-map "U"   'YaTeX-shift-section-up-region)
79  (define-key YaTeX-sectioning-buffer-map "D" 'YaTeX-shift-section-down-region)
80  (define-key YaTeX-sectioning-buffer-map "s"	'YaTeX-sync-section-buffer)
81  (define-key YaTeX-sectioning-buffer-map "n"
82    'YaTeX-sectioning-buffer-next-line)
83  (define-key YaTeX-sectioning-buffer-map "p"
84    'YaTeX-sectioning-buffer-prev-line)
85  (define-key YaTeX-sectioning-buffer-map "h"  'describe-mode)
86  (define-key YaTeX-sectioning-buffer-map "o"  'other-window)
87  (define-key YaTeX-sectioning-buffer-map "-"  'shrink-window)
88  (define-key YaTeX-sectioning-buffer-map "+"  'enlarge-window)
89  (define-key YaTeX-sectioning-buffer-map "q"  'delete-window)
90  (define-key YaTeX-sectioning-buffer-map "\C-_" 'YaTeX-shift-section-undo)
91  (and YaTeX-emacs-19 (boundp 'window-system) (eq window-system 'x)
92       (define-key YaTeX-sectioning-buffer-map [?\C-/]
93	 'YaTeX-shift-section-undo))
94  (YaTeX-sectioning-map-hide YaTeX-sectioning-buffer-map)
95)
96
97(defun YaTeX-sectioning-mode ()
98  "Mode for browsing document's sectioning structure.
99\\[YaTeX-shift-section-up]	Shift up a sectioning command
100\\[YaTeX-shift-section-down]	Shift down a sectioning command
101\\[YaTeX-shift-section-up-region]	Shift up sectioning commands in region
102\\[YaTeX-shift-section-down-region]	Shift down sectioning commands in region
103\\[YaTeX-shift-section-undo]	Undo changes of shifting
104\\[YaTeX-sync-section-buffer]	Synchronize sectioning buffer with source
105\\[YaTeX-sectioning-buffer-next-line]	Next line
106\\[YaTeX-sectioning-buffer-prev-line]	Previous line
107\\[YaTeX-sectioning-buffer-jump]	Previous line
108\\[YaTeX-sectioning-buffer-show]	Show curresponding source line
109"
110  (interactive)
111  (setq major-mode 'YaTeX-sectioning-mode
112	mode-name "sectioning")
113  (use-local-map YaTeX-sectioning-buffer-map)
114)
115
116(defvar YaTeX-sectioning-buffer-parent nil)
117(defun YaTeX-sectioning-buffer-jump-internal (&optional keep)
118  (let ((p (point))		;save-excursion is NG because
119	ptn ln)			;this function should switch buffer
120    (beginning-of-line)
121    (if (re-search-forward YaTeX-sectioning-regexp)
122	(progn
123	  (save-restriction
124	    (narrow-to-region (point-beginning-of-line) (point-end-of-line))
125	    (setq ptn (buffer-substring
126		       (1- (match-beginning 0))
127		       (progn (skip-chars-forward "^}") (1+ (point))))
128		  ln (buffer-substring
129		      (progn (search-forward "line:") (match-end 0))
130		      (progn (skip-chars-forward "0-9") (point)))))
131	  (goto-char p)
132	  (YaTeX-showup-buffer YaTeX-sectioning-buffer-parent nil t)
133	  (or
134	   (and ln (string< "" ln)
135		(progn
136		  (goto-char (point-min))
137		  (forward-line (max 0 (- (YaTeX-str2int ln) 2)))
138		  (and
139		   (search-forward ptn nil t)
140		   (goto-char (match-beginning 0)))))
141	   (progn
142	     (goto-char (point-max))
143	     (search-backward ptn)))
144	  (if keep (goto-buffer-window YaTeX-sectioning-buffer))
145	  (current-buffer))
146      nil)))
147
148(defun YaTeX-sectioning-buffer-jump (&optional keep)
149  "Goto corresponding sectioning unit with current line in the next window.
150If optional argument KEEP is non-nil, only shows the line."
151  (interactive)
152  (if (and YaTeX-sectioning-buffer-parent
153	   (get-buffer YaTeX-sectioning-buffer-parent))
154      (YaTeX-sectioning-buffer-jump-internal keep)
155    (message "No line number expression."))
156)
157
158(defun YaTeX-sectioning-buffer-show ()
159  "Show corresponding sectioning unit with current line."
160  (interactive)
161  (YaTeX-sectioning-buffer-jump-internal t)
162)
163
164(defun YaTeX-sectioning-hide-under (n)
165  "Hide sectioning commands under level N."
166  (let ((cw (selected-window)))
167    (YaTeX-showup-buffer YaTeX-sectioning-buffer nil t)
168    (if (>= n YaTeX-sectioning-max-level)
169	(progn
170	  (set-selective-display nil)
171	  (message "Show all."))
172      (set-selective-display (1+ n))
173      (if (rassq n YaTeX-sectioning-level)
174	  (message "Hide lower than %s" (car (rassq n YaTeX-sectioning-level)))
175	(message "")))
176    (if (numberp selective-display)
177	(setq mode-name (format "level %d" (1- selective-display)))
178      (setq mode-name (format "all")))
179    (select-window cw))
180)
181(defun YaTeX-sectioning-hide ()
182  "Call YaTeX-sectioning-hide-under with argument according to pressed key."
183  (interactive)
184  (YaTeX-sectioning-hide-under (- (YaTeX-last-key) ?0)))
185
186(defun YaTeX-sectioning-help ()
187  "Show help of sectioning."
188  (interactive)
189  (let ((cw (selected-window)) sb (hb (get-buffer-create "*Help*")))
190    (unwind-protect
191	(progn
192	  (other-window 1)
193	  (setq sb (current-buffer))
194	  (switch-to-buffer hb)
195	  (setq buffer-read-only nil)		;; Emacs26
196	  (erase-buffer)
197	  (insert "===== View sectioning =====
198C-p	Up sectioning level.			0	Show only \\part,
199C-n	Down sectioning level.			1	 and \\chapter,
200C-v	Scroll up *Sectioning line* buffer.	2	 and \\section,
201M-v	Scroll down *Sectioning line* buffer.	3	 and \\subsection,
202C-z	Scroll up by 1 line.			4	 and \\subsubsection,
203C-w	Scroll down by 1 line.			5	 and \\paragraph.
204SPC	Complete word.				6	Show all.
205TAB	Complete word.
206C-l	Recenter recent line.
207RET	Select.
208==== End of HELP =====
209")
210	  (set-buffer-modified-p nil)
211	  (goto-char (point-min))
212	  (momentary-string-display "" (point-min)))
213      (bury-buffer hb)
214      (switch-to-buffer sb)
215      (select-window cw)))
216)
217
218(defun YaTeX-sectioning-up (n)
219  "Up section level.
220Refers the YaTeX-read-section-in-minibuffer's local variable minibuffer-start."
221  (interactive "p")
222  (if (eq (selected-window) (minibuffer-window))
223      (let*((command (YaTeX-minibuffer-string))
224	    (aster (and (string< "" command)
225			(equal (substring command -1) "*")))
226	    (command (if aster (substring command 0 -1) command))
227	    (alist YaTeX-sectioning-level)
228	    (level 0))
229	(or (assoc command alist) (error "No such sectioning command."))
230	(while (not (string= (car (nth level alist)) command))
231	  (setq level (1+ level)))	;I want to use `member'....
232	(setq level (- level n))
233	(if (or (< level 0) (>= level (length alist)))
234	    (ding)
235	  (YaTeX-minibuffer-erase)
236	  (insert (concat (car (nth level alist)) (if aster "*" ""))))))
237)
238
239(defun YaTeX-sectioning-down (n)
240  "Down section level."
241  (interactive "p")
242  (YaTeX-sectioning-up (- n))
243)
244
245(defun YaTeX-sectioning-scroll-up (n)
246  (interactive "P")
247  (let ((section-buffer YaTeX-sectioning-buffer)
248	(cw (selected-window)))
249    (YaTeX-showup-buffer section-buffer nil t)
250    (unwind-protect
251	(scroll-up (or n (- (window-height) 2)))
252      (select-window cw)))
253)
254
255(defun YaTeX-sectioning-scroll-down (n)
256  (interactive "P")
257  (let ((section-buffer YaTeX-sectioning-buffer)
258	(cw (selected-window)))
259    (YaTeX-showup-buffer section-buffer nil t)
260    (unwind-protect
261	(scroll-down (or n (- (window-height) 2)))
262      (select-window cw)))
263)
264
265(defun YaTeX-sectioning-recenter (arg)
266  "Recenter `<<--' line"
267  (interactive "P")
268  (let ((cw (selected-window)))
269    (unwind-protect
270	(progn
271	  (YaTeX-showup-buffer YaTeX-sectioning-buffer nil t)
272	  (or (search-forward "<<--" nil t)
273	      (search-backward "<<--" nil))
274	  (recenter (or arg (/ (window-height) 2))))
275      (select-window cw)))
276)
277
278(defvar YaTeX-sectioning-minibuffer " *sectioning*"
279  "Miniuffer used for sectioning")
280;;;###autoload
281(defun YaTeX-read-section-in-minibuffer (prompt table &optional default delim)
282  (interactive)
283  (let ((minibuffer-completion-table table))
284    (read-from-minibuffer
285     prompt default YaTeX-sectioning-minibuffer-map))
286)
287
288(defun YaTeX-get-sectioning-level ()
289  "Get section-level on the cursor."
290   (cdr-safe (assoc (buffer-substring
291		     (point)
292		     (progn (skip-chars-forward "a-z") (point)))
293		     YaTeX-sectioning-level))
294)
295
296(defvar YaTeX-sectioning-buffer "*Sectioning lines*")
297(defvar YaTeX-sectioning-indent 1)
298(defun YaTeX-collect-sections ()
299  "Collect all the lines which contains sectioning command."
300  (let ((cw (selected-window)) level indent begp (prevp 1) (prevl 1)
301	(pattern (concat YaTeX-ec-regexp
302			 "\\(" YaTeX-sectioning-regexp "\\)\\*?{"))
303	(cb (current-buffer)))
304    (save-excursion
305      (set-buffer (get-buffer-create YaTeX-sectioning-buffer))
306      (setq buffer-read-only nil)
307      (erase-buffer)
308      (set-buffer cb)
309      (YaTeX-showup-buffer YaTeX-sectioning-buffer) ;show buffer
310      (goto-char (point-min))
311      (let ((standard-output (get-buffer YaTeX-sectioning-buffer)))
312	(while (re-search-forward pattern nil t)
313	  (goto-char (1+ (match-beginning 0)))
314	  (setq level (YaTeX-get-sectioning-level)
315		begp (match-beginning 0))
316	  ;;(beginning-of-line)
317	  ;;(skip-chars-forward " \t")
318	  (setq indent (format "%%%ds" (* level YaTeX-sectioning-indent)))
319	  (princ (format indent ""))
320	  (if (YaTeX-on-comment-p) (princ "%"))
321	  (princ (buffer-substring begp (progn (forward-list 1) (point))))
322	  (setq prevl (+ prevl (count-lines prevp (point)) -1)
323		prevp (point))
324	  (princ (format " (line:%d)" prevl))
325	  (princ "\n")))
326      (set-buffer YaTeX-sectioning-buffer)
327      (make-local-variable 'YaTeX-sectioning-buffer-parent)
328      (YaTeX-sectioning-mode)
329      (use-local-map YaTeX-sectioning-buffer-map)
330      (setq YaTeX-sectioning-buffer-parent cb)
331      (if (numberp selective-display)
332	  (setq mode-name (format "level %d" (1- selective-display))))
333      YaTeX-sectioning-buffer))
334)
335
336(defvar YaTeX-pending-undo nil)
337(defun YaTeX-section-overview ()
338  "Show section overview.  Return the nearest sectioning command."
339  (interactive)
340  (let ((cw (selected-window)) (ln (count-lines (point-min) (point)))
341	(pattern "(line:\\([0-9]+\\))")
342	secbuf (command ""))
343    (save-excursion
344      (setq secbuf (YaTeX-collect-sections))
345      (YaTeX-showup-buffer secbuf nil t)
346      (set-buffer secbuf)
347      (goto-char (point-max))
348      (while (re-search-backward pattern nil t)
349	(if (< ln (YaTeX-str2int (YaTeX-match-string 1))) nil
350	  (beginning-of-line)
351	  (search-forward YaTeX-ec)
352	  (looking-at YaTeX-TeX-token-regexp)
353	  (setq command (YaTeX-match-string 0))
354	  (end-of-line)
355	  (insert "  <<--")
356	  (setq pattern (concat "HackyRegexp" "ForFailure"))))
357      (set-buffer-modified-p nil)
358      (setq buffer-read-only t buffer-undo-list nil)
359      (make-local-variable 'YaTeX-pending-undo)
360      (forward-line 1)
361      (if (eobp) (recenter -1) (recenter -3))
362      (select-window cw)
363      command))
364)
365
366;;;###autoload
367(defun YaTeX-make-section-with-overview ()
368  "Input sectining command with previous overview."
369  (interactive)
370  (insert
371   YaTeX-ec
372   (YaTeX-read-section-in-minibuffer
373    "Sectioning(Up=C-p, Down=C-n, Help=?): "
374    YaTeX-sectioning-level (YaTeX-section-overview))
375   "{}")
376  (forward-char -1)
377)
378
379(defun YaTeX-shifted-section (sc n)
380  "Get SC's N-shifted sectioning command."
381  (let (lv)
382    (setq lv (- (cdr (assoc sc YaTeX-sectioning-level)) n)
383	  lv (max (min YaTeX-sectioning-max-level lv) 0))
384    (car (nth lv YaTeX-sectioning-level)))
385)
386
387(defun YaTeX-shift-section-up (n)
388  "Shift sectioning command down by level N."
389  (interactive "p")
390  (let ((cb (current-buffer)) sc nsc lv)
391    (if (and YaTeX-sectioning-buffer-parent
392	     (get-buffer YaTeX-sectioning-buffer-parent)
393	     (save-excursion
394	       (beginning-of-line)
395	       (skip-chars-forward "^\\\\" (point-end-of-line))
396	       (YaTeX-on-section-command-p YaTeX-sectioning-regexp)))
397	(save-excursion
398	  (or (buffer-name (get-buffer YaTeX-sectioning-buffer-parent))
399	      (error "This buffer is obsolete."))
400	  (setq nsc (YaTeX-shifted-section (YaTeX-match-string 1) n))
401	  (YaTeX-sectioning-buffer-jump-internal)
402	  (undo-boundary)
403	  (goto-char (match-beginning 0))
404	  (skip-chars-forward "\\\\")
405	  (delete-region
406	   (point) (progn (skip-chars-forward "^*{") (point)))
407	  (insert nsc)
408	  (undo-boundary)
409	  ;; Return to *Sectioning Lines* buffer
410	  (select-window (get-buffer-window cb))
411	  (beginning-of-line)
412	  (let (buffer-read-only)
413	    (delete-region
414	     (point) (progn (skip-chars-forward " \t") (point)))
415	    (indent-to-column (* (cdr (assoc nsc YaTeX-sectioning-level))
416				 YaTeX-sectioning-indent))
417	    (skip-chars-forward "^\\\\")
418	    (delete-region
419	     (1+ (point)) (progn (skip-chars-forward "^*{") (point)))
420	    (insert nsc)
421	    (undo-boundary))
422	  (set-buffer-modified-p nil)
423	  (setq YaTeX-pending-undo pending-undo-list)
424	  )))
425)
426(defun YaTeX-shift-section-down (n)
427  "Shift sectioning command down by level N."
428  (interactive "p")
429  (YaTeX-shift-section-up (- n))
430)
431(defun YaTeX-shift-section-undo (arg)
432  "Undo YaTeX-shift-section-up/down."
433  (interactive "p")
434  (and YaTeX-sectioning-buffer-parent
435       (get-buffer YaTeX-sectioning-buffer-parent)
436       (equal (current-buffer) (get-buffer YaTeX-sectioning-buffer))
437       (let ((cb (current-buffer))
438	     (lc (if (eq last-command 'YaTeX-shift-section-undo) 'undo t)))
439	 (let ((pending-undo-list YaTeX-pending-undo)
440	       buffer-read-only (last-command lc))
441	   (undo arg)
442	   (setq YaTeX-pending-undo pending-undo-list))
443	 (YaTeX-showup-buffer YaTeX-sectioning-buffer-parent)
444	 (goto-buffer-window YaTeX-sectioning-buffer-parent)
445	 (undo-boundary)
446	 (let ((last-command lc)
447	       (pending-undo-list
448		(if (eq lc 'undo) YaTeX-pending-undo pending-undo-list)))
449	   (undo arg)
450	   (setq YaTeX-pending-undo pending-undo-list))
451	 (goto-buffer-window cb)
452	 (setq this-command 'YaTeX-shift-section-undo)))
453)
454(defun YaTeX-sync-section-buffer ()
455  "Synchronize *Sectioning Lines* buffer with parent buffer."
456  (interactive)
457  (if (and YaTeX-sectioning-buffer-parent
458	   (get-buffer YaTeX-sectioning-buffer-parent))
459      (let ((cb (current-buffer)) (p (point)))
460	(set-buffer (get-buffer YaTeX-sectioning-buffer-parent))
461	(YaTeX-section-overview)
462	(switch-to-buffer cb)
463	(goto-char p)))
464)
465(defun YaTeX-shift-section-up-region (beg end n)
466  "Shift sectioning commands in region down by level N."
467  (interactive "r\np")
468  (or YaTeX-sectioning-buffer-parent
469      (get-buffer YaTeX-sectioning-buffer-parent)
470      (error "Can't find corresponding LaTeX buffer"))
471  (save-excursion
472    (goto-char beg)
473    (let ((cb (current-buffer)) nsc from to repllist (e (make-marker)))
474      (set-marker e end)
475      (while (progn (skip-chars-forward "^\\\\") (< (point) e))
476	(YaTeX-on-section-command-p YaTeX-sectioning-regexp)
477	(setq from (YaTeX-match-string 0)
478	      nsc (YaTeX-shifted-section (YaTeX-match-string 1) n))
479	(goto-char (match-beginning 0))
480	(let (buffer-read-only)
481	  ;(delete-region (point) (progn (beginning-of-line) (point)))
482	  (delete-region (progn (beginning-of-line) (point))
483			 (progn (skip-chars-forward " \t") (point)))
484	  (indent-to-column (cdr (assoc nsc YaTeX-sectioning-level)))
485	  (delete-region
486	   (progn (skip-chars-forward "%\\\\") (point))
487	   (progn (skip-chars-forward "^*{") (point)))
488	  (insert nsc))
489	(YaTeX-on-section-command-p YaTeX-sectioning-regexp)
490	(setq to (YaTeX-match-string 0)
491	      repllist (cons (cons from to) repllist))
492	(forward-line 1))
493      (YaTeX-showup-buffer YaTeX-sectioning-buffer-parent)
494      (goto-buffer-window YaTeX-sectioning-buffer-parent)
495      (save-excursion
496	(goto-char (point-max))
497	(undo-boundary)
498	(while repllist
499	  (if (search-backward (car (car repllist)) nil t)
500	      (progn
501		(goto-char (match-beginning 0))	;confirm
502		(delete-region (point) (match-end 0))
503		(insert (cdr (car repllist)))
504		(goto-char (match-beginning 0))))
505	  (setq repllist (cdr repllist))))
506      (goto-buffer-window cb)))
507)
508(defun YaTeX-shift-section-down-region (beg end n)
509  "Shift sectioning commands in region down by level N."
510  (interactive "r\np")
511  (YaTeX-shift-section-up-region beg end (- n))
512)
513(defun YaTeX-sectioning-buffer-next-line (n)
514  "Move to next line in *Sectioning Lines* buffer."
515  (interactive "p")
516  (forward-line n)
517  (skip-chars-forward " \t%")
518)
519(defun YaTeX-sectioning-buffer-prev-line (n)
520  "Move to previous line in *Sectioning Lines* buffer."
521  (interactive "p")
522  (YaTeX-sectioning-buffer-next-line (- n))
523)
524(provide 'yatexsec)
525