forked from brotzeit/rustic
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrustic-interaction.el
379 lines (340 loc) · 16.5 KB
/
rustic-interaction.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
;;; rustic-interaction.el --- Common interactive functions -*-lexical-binding: t-*-
;;; Code:
(defun rustic-rewind-to-beginning-of-current-level-expr ()
(let ((current-level (rustic-paren-level)))
(back-to-indentation)
(when (looking-at "->")
(rustic-rewind-irrelevant)
(back-to-indentation))
(while (> (rustic-paren-level) current-level)
(backward-up-list)
(back-to-indentation))
;; When we're in the where clause, skip over it. First find out the start
;; of the function and its paren level.
(let ((function-start nil) (function-level nil))
(save-excursion
(rustic-beginning-of-defun)
(back-to-indentation)
;; Avoid using multiple-value-bind
(setq function-start (point)
function-level (rustic-paren-level)))
;; On a where clause
(when (or (rustic-looking-at-where)
;; or in one of the following lines, e.g.
;; where A: Eq
;; B: Hash <- on this line
(and (save-excursion
(rustic-rewind-to-where function-start))
(= current-level function-level)))
(goto-char function-start)))))
(defun rustic-align-to-expr-after-brace ()
(save-excursion
(forward-char)
;; We don't want to indent out to the open bracket if the
;; open bracket ends the line
(when (not (looking-at "[[:blank:]]*\\(?://.*\\)?$"))
(when (looking-at "[[:space:]]")
(forward-word 1)
(backward-word 1))
(current-column))))
(defun rustic-align-to-method-chain ()
(save-excursion
;; for method-chain alignment to apply, we must be looking at
;; another method call or field access or something like
;; that. This avoids rather "eager" jumps in situations like:
;;
;; {
;; something.foo()
;; <indent>
;;
;; Without this check, we would wind up with the cursor under the
;; `.`. In an older version, I had the inverse of the current
;; check, where we checked for situations that should NOT indent,
;; vs checking for the one situation where we SHOULD. It should be
;; clear that this is more robust, but also I find it mildly less
;; annoying to have to press tab again to align to a method chain
;; than to have an over-eager indent in all other cases which must
;; be undone via tab.
(when (looking-at (concat "\s*\." rustic-re-ident))
(forward-line -1)
(end-of-line)
;; Keep going up (looking for a line that could contain a method chain)
;; while we're in a comment or on a blank line. Stop when the paren
;; level changes.
(let ((level (rustic-paren-level)))
(while (and (or (rustic-in-str-or-cmnt)
;; Only whitespace (or nothing) from the beginning to
;; the end of the line.
(looking-back "^\s*" (point-at-bol)))
(= (rustic-paren-level) level))
(forward-line -1)
(end-of-line)))
(let
;; skip-dot-identifier is used to position the point at the
;; `.` when looking at something like
;;
;; foo.bar
;; ^ ^
;; | |
;; | position of point
;; returned offset
;;
((skip-dot-identifier
(lambda ()
(when (and (rustic-looking-back-ident) (save-excursion (forward-thing 'symbol -1) (= ?. (char-before))))
(forward-thing 'symbol -1)
(backward-char)
(- (current-column) rustic-indent-offset)))))
(cond
;; foo.bar(...)
((rustic-looking-back-str ")")
(backward-list 1)
(funcall skip-dot-identifier))
;; foo.bar
(t (funcall skip-dot-identifier)))))))
;;;;;;;;;;;;;;;;
;; Interactive
(defun rustic-indent-line ()
(interactive)
(let ((indent
(save-excursion
(back-to-indentation)
;; Point is now at beginning of current line
(let* ((level (rustic-paren-level))
(baseline
;; Our "baseline" is one level out from the indentation of the expression
;; containing the innermost enclosing opening bracket. That
;; way if we are within a block that has a different
;; indentation than this mode would give it, we still indent
;; the inside of it correctly relative to the outside.
(if (= 0 level)
0
(or
(when rustic-indent-method-chain
(rustic-align-to-method-chain))
(save-excursion
(rustic-rewind-irrelevant)
(backward-up-list)
(rustic-rewind-to-beginning-of-current-level-expr)
(+ (current-column) rustic-indent-offset))))))
(cond
;; Indent inside a non-raw string only if the the previous line
;; ends with a backslash that is inside the same string
((nth 3 (syntax-ppss))
(let*
((string-begin-pos (nth 8 (syntax-ppss)))
(end-of-prev-line-pos (when (> (line-number-at-pos) 1)
(save-excursion
(forward-line -1)
(end-of-line)
(point)))))
(when
(and
;; If the string begins with an "r" it's a raw string and
;; we should not change the indentation
(/= ?r (char-after string-begin-pos))
;; If we're on the first line this will be nil and the
;; rest does not apply
end-of-prev-line-pos
;; The end of the previous line needs to be inside the
;; current string...
(> end-of-prev-line-pos string-begin-pos)
;; ...and end with a backslash
(= ?\\ (char-before end-of-prev-line-pos)))
;; Indent to the same level as the previous line, or the
;; start of the string if the previous line starts the string
(if (= (line-number-at-pos end-of-prev-line-pos) (line-number-at-pos string-begin-pos))
;; The previous line is the start of the string.
;; If the backslash is the only character after the
;; string beginning, indent to the next indent
;; level. Otherwise align with the start of the string.
(if (> (- end-of-prev-line-pos string-begin-pos) 2)
(save-excursion
(goto-char (+ 1 string-begin-pos))
(current-column))
baseline)
;; The previous line is not the start of the string, so
;; match its indentation.
(save-excursion
(goto-char end-of-prev-line-pos)
(back-to-indentation)
(current-column))))))
;; A function return type is indented to the corresponding
;; function arguments, if -to-arguments is selected.
((and rust-indent-return-type-to-arguments
(looking-at "->"))
(save-excursion
(backward-list)
(or (rustic-align-to-expr-after-brace)
(+ baseline rustic-indent-offset))))
;; A closing brace is 1 level unindented
((looking-at "[]})]") (- baseline rustic-indent-offset))
;; Doc comments in /** style with leading * indent to line up the *s
((and (nth 4 (syntax-ppss)) (looking-at "*"))
(+ 1 baseline))
;; When the user chose not to indent the start of the where
;; clause, put it on the baseline.
((and (not rustic-indent-where-clause)
(rustic-looking-at-where))
baseline)
;; If we're in any other token-tree / sexp, then:
(t
(or
;; If we are inside a pair of braces, with something after the
;; open brace on the same line and ending with a comma, treat
;; it as fields and align them.
(when (> level 0)
(save-excursion
(rustic-rewind-irrelevant)
(backward-up-list)
;; Point is now at the beginning of the containing set of braces
(rustic-align-to-expr-after-brace)))
;; When where-clauses are spread over multiple lines, clauses
;; should be aligned on the type parameters. In this case we
;; take care of the second and following clauses (the ones
;; that don't start with "where ")
(save-excursion
;; Find the start of the function, we'll use this to limit
;; our search for "where ".
(let ((function-start nil) (function-level nil))
(save-excursion
;; If we're already at the start of a function,
;; don't go back any farther. We can easily do
;; this by moving to the end of the line first.
(end-of-line)
(rustic-beginning-of-defun)
(back-to-indentation)
;; Avoid using multiple-value-bind
(setq function-start (point)
function-level (rustic-paren-level)))
;; When we're not on a line starting with "where ", but
;; still on a where-clause line, go to "where "
(when (and
(not (rustic-looking-at-where))
;; We're looking at something like "F: ..."
(looking-at (concat rustic-re-ident ":"))
;; There is a "where " somewhere after the
;; start of the function.
(rustic-rewind-to-where function-start)
;; Make sure we're not inside the function
;; already (e.g. initializing a struct) by
;; checking we are the same level.
(= function-level level))
;; skip over "where"
(forward-char 5)
;; Unless "where" is at the end of the line
(if (eolp)
;; in this case the type parameters bounds are just
;; indented once
(+ baseline rustic-indent-offset)
;; otherwise, skip over whitespace,
(skip-chars-forward "[:space:]")
;; get the column of the type parameter and use that
;; as indentation offset
(current-column)))))
(progn
(back-to-indentation)
;; Point is now at the beginning of the current line
(if (or
;; If this line begins with "else" or "{", stay on the
;; baseline as well (we are continuing an expression,
;; but the "else" or "{" should align with the beginning
;; of the expression it's in.)
;; Or, if this line starts a comment, stay on the
;; baseline as well.
(looking-at "\\<else\\>\\|{\\|/[/*]")
;; If this is the start of a top-level item,
;; stay on the baseline.
(looking-at rustic-top-item-beg-re)
(save-excursion
(rustic-rewind-irrelevant)
;; Point is now at the end of the previous line
(or
;; If we are at the start of the buffer, no
;; indentation is needed, so stay at baseline...
(= (point) 1)
;; ..or if the previous line ends with any of these:
;; { ? : ( , ; [ }
;; then we are at the beginning of an expression, so stay on the baseline...
(looking-back "[(,:;?[{}]\\|[^|]|" (- (point) 2))
;; or if the previous line is the end of an attribute, stay at the baseline...
(progn (rustic-rewind-to-beginning-of-current-level-expr) (looking-at "#")))))
baseline
;; Otherwise, we are continuing the same expression from the previous line,
;; so add one additional indent level
(+ baseline rustic-indent-offset))))))))))
(when indent
;; If we're at the beginning of the line (before or at the current
;; indentation), jump with the indentation change. Otherwise, save the
;; excursion so that adding the indentations will leave us at the
;; equivalent position within the line to where we were before.
(if (<= (current-column) (current-indentation))
(indent-line-to indent)
(save-excursion (indent-line-to indent))))))
(defun rustic-promote-module-into-dir ()
"Promote the module file visited by the current buffer into its own directory.
For example, if the current buffer is visiting the file `foo.rs',
then this function creates the directory `foo' and renames the
file to `foo/mod.rs'. The current buffer will be updated to
visit the new file."
(interactive)
(let ((filename (buffer-file-name)))
(if (not filename)
(message "Buffer is not visiting a file.")
(if (string-equal (file-name-nondirectory filename) "mod.rs")
(message "Won't promote a module file already named mod.rs.")
(let* ((basename (file-name-sans-extension
(file-name-nondirectory filename)))
(mod-dir (file-name-as-directory
(concat (file-name-directory filename) basename)))
(new-name (concat mod-dir "mod.rs")))
(mkdir mod-dir t)
(rename-file filename new-name 1)
(set-visited-file-name new-name))))))
;;; Defun Motions
(defun rustic-beginning-of-defun (&optional arg)
"Move backward to the beginning of the current defun.
With ARG, move backward multiple defuns. Negative ARG means
move forward.
This is written mainly to be used as `beginning-of-defun-function' for Rust.
Don't move to the beginning of the line. `beginning-of-defun',
which calls this, does that afterwards."
(interactive "p")
(let* ((arg (or arg 1))
(magnitude (abs arg))
(sign (if (< arg 0) -1 1)))
;; If moving forward, don't find the defun we might currently be
;; on.
(when (< sign 0)
(end-of-line))
(catch 'done
(dotimes (_ magnitude)
;; Search until we find a match that is not in a string or comment.
(while (if (re-search-backward (concat "^\\(" rustic-top-item-beg-re "\\)")
nil 'move sign)
(rustic-in-str-or-cmnt)
;; Did not find it.
(throw 'done nil)))))
t))
(defun rustic-end-of-defun ()
"Move forward to the next end of defun.
With argument, do it that many times.
Negative argument -N means move back to Nth preceding end of defun.
Assume that this is called after beginning-of-defun. So point is
at the beginning of the defun body.
This is written mainly to be used as `end-of-defun-function' for Rust."
(interactive)
;; Find the opening brace
(if (re-search-forward "[{]" nil t)
(progn
(goto-char (match-beginning 0))
;; Go to the closing brace
(condition-case nil
(forward-sexp)
(scan-error
;; The parentheses are unbalanced; instead of being unable to fontify, just jump to the end of the buffer
(goto-char (point-max)))))
;; There is no opening brace, so consider the whole buffer to be one "defun"
(goto-char (point-max))))
(provide 'rustic-interaction)
;;; rustic-interaction.el ends here