-
Notifications
You must be signed in to change notification settings - Fork 4
/
csh-indent-function.el
270 lines (253 loc) · 11.6 KB
/
csh-indent-function.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
(defconst csh-comment-regexp "^\\s *#")
(defconst csh-label-re "^\\s *[^!#$\n ]+:")
(defconst csh-case-item-re "^\\s *\\(case .*\\|default\\):")
(defconst csh-keywords-re "^\\s *\\(else\\b\\|foreach\\b\\|if\\b.+\\(\\\\\\|\\bthen\\b\\)\\|switch\\b\\|while\\b\\)")
(defconst csh-case-default-re "^\\s *\\(case\\|default\\)\\b")
(defconst csh-if-re "^\\s *if\\b.+\\(\\\\\\|\\bthen\\b\\)")
(defconst csh-else-re "^\\s *\\belse\\(\\b\\|$\\)")
(defconst csh-else-if-re "^\\s *\\belse if\\(\\b\\|$\\)")
(defconst csh-endif-re "^\\s *endif\\b")
(defconst csh-end-re "^\\s *end\\b")
(defconst csh-endsw-re "^\\s *endsw\\b")
(defconst csh-switch-re "^\\s *switch\\b")
(defconst csh-iteration-keywords-re "^[^#\n]*\\s\"*\\b\\(while\\|foreach\\)\\b")
(defconst csh-multiline-re "^.*\\\\$")
(defvar csh-indent 4)
(defvar csh-case-item-offset csh-indent
"Additional indentation for case items within a case statement.")
(defvar csh-match-and-tell t
"If non-nil echo in the minibuffer the matching compound command
for the \"breaksw\", \"end\", or \"endif\".")
(defun csh-indent-line ()
"Indent current line as far as it should go according
to the syntax/context"
(interactive)
(let (case-fold-search)
(save-excursion
(beginning-of-line)
(unless (bobp)
;; Align this line to current nesting level
(let* ((level-list (csh-get-nest-level)) ; Where to nest against
(this-line-level (current-indentation))
(nester-column (csh-get-nester-column (cdr level-list)))
(struct-match (csh-match-structure-and-reindent)))
(when struct-match
(setq nester-column struct-match))
(unless (eq nester-column this-line-level)
(beginning-of-line)
(let ((beg (point)))
(back-to-indentation)
(delete-region beg (point)))
(indent-to nester-column)))))
;; Position point on this line
(let* ((this-line-level (current-indentation))
(this-bol (save-excursion
(beginning-of-line)
(point)))
(this-point (- (point) this-bol)))
(when (> this-line-level this-point);; point in initial white space
(back-to-indentation)))))
(defun csh-indent-region (start end)
"From start to end, indent each line."
;; The algorithm is just moving through the region line by line with
;; the match noise turned off. Only modifies nonempty lines.
(save-excursion
(let (csh-match-and-tell
(endmark (copy-marker end)))
(goto-char start)
(beginning-of-line)
(setq start (point))
(while (> (marker-position endmark) start)
(unless (and (bolp) (eolp))
(csh-indent-line))
(forward-line 1)
(setq start (point)))
(set-marker endmark nil))))
(defun csh-match-indent-level (begin-re end-re)
"Match the compound command and indent. Return nil on no match,
indentation to use for this line otherwise."
(interactive)
(let* ((case-fold-search)
(nest-list
(save-excursion
(csh-get-compound-level begin-re end-re (point)))))
(if nest-list
(let* ((nest-level (car nest-list))
(match-line (cdr nest-list)))
(when csh-match-and-tell
(save-excursion
(goto-line match-line)
(message "Matched ... %s" (csh-line-to-string))))
nest-level)
(when csh-match-and-tell
(message "No matching compound command"))
nil))) ;; Propagate a miss.
(defun csh-get-nest-level ()
"Return a 2 element list (nest-level nest-line) describing where the
current line should nest."
(let (case-fold-search level)
(save-excursion
(forward-line -1)
(while (not (or (bobp) level))
(if (not (or (looking-at "^\\s *$")
(save-excursion
(forward-line -1)
(beginning-of-line)
(looking-at csh-multiline-re))
(looking-at csh-comment-regexp)))
(setq level (cons (current-indentation)
(csh-current-line)))
(forward-line -1)))
(if level level (cons (current-indentation) (csh-current-line))))))
(defun csh-current-line ()
"Return the vertical position of point in the buffer.
Top line is 1."
(+ (count-lines (point-min) (point))
(if (= (current-column) 0) 1 0)))
(defun csh-get-nester-column (nest-line)
"Return the column to indent to with respect to nest-line taking
into consideration keywords and other nesting constructs."
(save-excursion
(let ((fence-post)
(case-fold-search)
(start-line (csh-current-line)))
;; Handle case item indentation constructs for this line
(cond ((looking-at csh-case-item-re)
;; This line is a case item...
(save-excursion
(goto-line nest-line)
(let ((fence-post (save-excursion (end-of-line) (point))))
(cond ((re-search-forward csh-switch-re fence-post t)
;; If this is the first case under the switch, indent.
(goto-char (match-beginning 0))
(+ (current-indentation) csh-case-item-offset))
((re-search-forward csh-case-item-re fence-post t)
;; If this is another case right under a previous case
;; without intervening code, stay at the same
;; indentation.
(goto-char (match-beginning 0))
(current-indentation))
(t
;; Else, this is a new case. Outdent.
(- (current-indentation) csh-case-item-offset))))))
(t;; Not a case-item. What to do relative to the nest-line?
(save-excursion
(goto-line nest-line)
(setq fence-post (save-excursion (end-of-line) (point)))
(save-excursion
(cond
;; Check if we are in a continued statement
((and (looking-at csh-multiline-re)
(save-excursion
(goto-line (1- start-line))
(looking-at csh-multiline-re)))
(if (looking-at ".*[\'\"]\\\\")
;; If this is a continued string, indent under
;; opening quote.
(progn
(re-search-forward "[\'\"]")
(forward-char -1))
(if (looking-at ".*([^\)\n]*\\\\")
;; Else if this is a continued parenthesized
;; list, indent after paren.
(re-search-forward "(" fence-post t)
;; Else, indent after whitespace after first word.
(re-search-forward "[^ \t]+[ \t]+" fence-post t)))
(current-column))
;; In order to locate the column of the keyword,
;; which might be embedded within a case-item,
;; it is necessary to use re-search-forward.
;; Search by literal case, since shell is
;; case-sensitive.
((re-search-forward csh-keywords-re fence-post t)
(goto-char (match-beginning 1))
(if (looking-at csh-switch-re)
(+ (current-indentation) csh-case-item-offset)
(+ (current-indentation)
(if (null csh-indent)
2 csh-indent))))
((re-search-forward csh-case-default-re fence-post t)
(if csh-indent
(progn
(goto-char (match-beginning 1))
(+ (current-indentation) csh-indent))
(goto-char (match-end 1))
(+ (current-indentation) 1)))
;; Now detect first statement under a case item
((looking-at csh-case-item-re)
(if csh-case-indent
(+ (current-indentation) csh-case-indent)
(re-search-forward csh-case-item-re fence-post t)
(goto-char (match-end 1))
(+ (current-column) 1)))
;; If this is the first statement under a control-flow
;; label, indent one level.
((csh-looking-at-label)
(+ (current-indentation) csh-indent))
;; This is hosed when using current-column
;; and there is a multi-command expression as the
;; nester.
(t (current-indentation))))))))))
(defun csh-looking-at-label ()
"Return true if current line is a label (not the default: case label)."
(and (looking-at csh-label-re)
(not (looking-at "^\\s *default:"))))
(defun csh-match-structure-and-reindent ()
"If the current line matches one of the indenting keywords
or one of the control structure ending keywords then reindent. Also
if csh-match-and-tell is non-nil the matching structure will echo in
the minibuffer"
(interactive)
(let (case-fold-search)
(save-excursion
(beginning-of-line)
(cond ((looking-at csh-else-re)
(csh-match-indent-level csh-if-re csh-endif-re))
((looking-at csh-else-if-re)
(csh-match-indent-level csh-if-re csh-endif-re))
((looking-at csh-endif-re)
(csh-match-indent-level csh-if-re csh-endif-re))
((looking-at csh-end-re)
(csh-match-indent-level csh-iteration-keywords-re csh-end-re))
((looking-at csh-endsw-re)
(csh-match-indent-level csh-switch-re csh-endsw-re))
((csh-looking-at-label)
;; Flush control-flow labels left since they don't nest.
0)))))
(defun csh-get-compound-level
(begin-re end-re anchor-point &optional balance-list)
"Determine how much to indent this structure. Return a list (level line)
of the matching compound command or nil if no match found."
(let* ((match-point (if (re-search-backward begin-re (point-min) t)
(match-beginning 0) 0))
;; Locate the next compound begin keyword bounded by point-min
(nest-column (if (zerop match-point)
1
(goto-char match-point)
(current-indentation)))
(nest-list (cons 0 0))) ;; sentinel cons since cdr is >= 1
(unless (zerop match-point)
(when (nlistp balance-list)
(setq balance-list (list)))
;; Now search forward from matching start keyword for end keyword
(while (and (consp nest-list)
(zerop (cdr nest-list))
(re-search-forward end-re anchor-point t))
(unless (memq (point) balance-list)
(setq balance-list (cons (point) balance-list))
(goto-char match-point) ;; beginning of compound cmd
(setq nest-list
(csh-get-compound-level begin-re end-re
anchor-point balance-list))))
(when (consp nest-list)
(if (zerop (cdr nest-list))
(progn
(goto-char match-point)
(cons nest-column (csh-current-line)))
nest-list)))))
(defun csh-line-to-string ()
"From point, construct a string from all characters on
current line"
(skip-chars-forward " \t") ;; skip tabs as well as spaces
(buffer-substring (point) (progn (end-of-line 1) (point))))
(provide 'csh-indent-function)