-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathvertical-space-cleanup.el
171 lines (135 loc) · 5.49 KB
/
vertical-space-cleanup.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
;;; vertical-space-cleanup.el - Cleanup Spacing Between Markdown/Org headers
;; This package is designed to ensure that documents have a consistent number of
;; blank lines between headers, for both markdown files and org-mode files.
;;
;; For example this might be a Markdown file:
;;
;; /
;; | # Intro
;; | This is my introduction
;; | ## Authors
;; | Steve
;; |
;; | ## References
;; | 1...
;; \
;;
;; Here we see there is a difference in the vertical whitespace between the
;; two second-level headers "## Authors" and "## References". Using this
;; package the `vertical-space-cleanup' command can be invoked and will
;; reformat all headers to have an identical number of newlines between
;; the header and any previous content.
;;
;; The configuration of the spacing depends upon the header-level. In my
;; personal configuration:
;;
;; # Level 1 heading - four newlines before it.
;; ## Level 2 heading - three newlines before it.
;; ### Level 3 heading - two newlines before it.
;; #### Level 4 heading - one newlines before it.
;;
;; The choice is made by looking at the Nth element of the list with name
;; `vertical-space-cleanup-md' - a similar list exists for org-mode - after
;; removing any previous newlines.
;;
;; BUGS:
;;
;; - None known, please report if you see something wrong.
;;
;;; Configuration
;; There is only one configuration value supported, which is the mapping
;; of the header-level to the number of newlines to add.
;;
;; The following two lists handle that.
;; TODO: We should probably use an alist, keyed on the mode, to get
;; the values as this would allow easier updates for additional modes.
;;
(defvar vertical-space-cleanup-md
(list
"\n\n\n\n" ;; level 1 - 4 newlines before it.
"\n\n\n" ;; level 2 - 3 newlines before it.
"\n\n" ;; level 3 - 2 newlines before it.
"\n" ;; level 4 - 1 newline before it.
)) ;; other levels get zero.
(defvar vertical-space-cleanup-org
(list
"\n" ;; level 1 - 1 newline before it.
)) ;; other levels get zero.
;;; Implementation
(defun count-chars (sep str)
"Helper function to count how many times the given character occurs in the specified string."
(let ((qsep (regexp-quote sep)))
(- (length (split-string str qsep)) 1)))
(defun vertical-space-cleanup-header-regexp ()
"Return the appropriate regexp for finding headers.
This supports `markdown-mode', and `org-mode', along with anything derived from either."
(cond
((derived-mode-p 'markdown-mode) "^\n*#+")
((derived-mode-p 'org-mode) "^\n*\\*+")))
(defun vertical-space-cleanup-get-header-level (header)
"Return the header depth level.
This supports `markdown-mode', and `org-mode', along with anything derived from either."
(interactive)
(cond
((derived-mode-p 'markdown-mode) (count-chars "#" header))
((derived-mode-p 'org-mode) (count-chars "*" header))))
(defun vertical-space-cleanup-get-newlines (level)
"Return the string of newlines for the given mode.
This is based upon the header level, and is configurable via the two lists
`vertical-space-cleanup-md' and `vertical-space-cleanup-org'."
(cond
((derived-mode-p 'markdown-mode) (nth (- level 1) vertical-space-cleanup-md))
((derived-mode-p 'org-mode) (nth (- level 1) vertical-space-cleanup-org))))
(defun vertical-space-cleanup ()
"Ensure there is consistent whitespace between headers and previous sections.
We iterate over the buffer and start by removing all newlines between headers,
then we iterate again and add a specific number of newlines between headers
based on the level of indentation.
This function will operate upon either `markdown-mode' or `org-mode', as well as
any modes derived from either of those."
(interactive)
(save-excursion
(outline-show-all)
(goto-char (point-min))
;; For each header
(while (re-search-forward (vertical-space-cleanup-header-regexp) nil t)
;; get the header, and count the depth of it.
(let* ((match (match-string 0))
(lines (count-chars "\n" match)))
;; move to start of line
(beginning-of-line)
;; remove each newline
(while (> lines 0)
(delete-backward-char 1)
(setq lines (- lines 1))))
;; advance to next line, so we don't loop forever
(end-of-line))
;; Need to start at the beginning of the file again.
(goto-char (point-min))
;; now add new lines
;; for each header
(while (re-search-forward (vertical-space-cleanup-header-regexp) nil t)
;; count the depth of it
(let* ((match (match-string 0))
(level (vertical-space-cleanup-get-header-level match)))
;; move to start of line
(beginning-of-line)
;; insert newlines, based on header level
(if (vertical-space-cleanup-get-newlines level)
(insert (vertical-space-cleanup-get-newlines level)))
;; forward to avoid infinite loops
(end-of-line))))
(vertical-space-cleanup-kill-leading-whitespace))
(defun vertical-space-cleanup-kill-leading-whitespace ()
"Remove whitespace at the start of the file.
This is largely required because the markdown setup will add four newlines before
a level-one header, by default, and those typically occur at the start of the documents.
"
(save-excursion
(outline-show-all)
(goto-char (point-min))
(delete-region (point)
(+ (save-excursion (skip-chars-forward " \n"))
(point)))))
;; Provide the package
(provide 'vertical-space-cleanup)