Skip to content

Commit 99ff376

Browse files
committed
Improve functions related to defuns
More robust and sensible behaviors.
1 parent 82ee822 commit 99ff376

File tree

3 files changed

+266
-52
lines changed

3 files changed

+266
-52
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ To install without MELPA, download [latest release](https://github.com/swift-ema
6161
]
6262
```
6363
- `forward-sexp`
64-
- `beginning-of-defun` and `end-of-defun`, so `mark-defun` and `narrow-to-defun` work fine.
64+
- `beginning-of-defun`, `end-of-defun`, `mark-defun`, and `narrow-to-defun`.
6565
- `indent-new-comment-line`
6666
- [Imenu](https://www.gnu.org/software/emacs/manual/html_node/emacs/Imenu.html)
6767
- Running Swift REPL in a buffer (`M-x run-swift`)

swift-mode-beginning-of-defun.el

Lines changed: 256 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -25,68 +25,114 @@
2525

2626
;;; Commentary:
2727

28-
;; beginning-of-defun and end-of-defun
28+
;; `beginning-of-defun' and `end-of-defun'
29+
;;
30+
;; The end of a defun is just after the close curly brace.
31+
;;
32+
;; The beginning of a defun is the beginning of:
33+
;; - "func" keyword,
34+
;; - its modifiers or attributes,
35+
;; - comments on the same line.
36+
;;
37+
;; `swift-mode:beginning-of-defun' moves the point to the beginning of a defun
38+
;; that precedes (if the arg is positive) or follows (if the arg is negative)
39+
;; the original point and has the same or less nesting level.
40+
;;
41+
;; `swift-mode:end-of-defun' moves the point to the end of a defun
42+
;; that follows (if the arg is positive) or precedes (if the arg is negative)
43+
;; the original point and has the same or less nesting level.
2944

3045
;;; Code:
3146

3247
(require 'swift-mode-lexer)
3348
(require 'swift-mode-indent)
3449

50+
;;;###autoload
51+
(defcustom swift-mode:mark-defun-preference 'containing
52+
"Preference for `swift-mode:mark-defun' for nested declarations.
53+
54+
Suppose the following code with the point located at A:
55+
56+
func outer() {
57+
func inner1() {
58+
}
59+
60+
// A
61+
62+
func inner2() {
63+
}
64+
}
65+
66+
If `swift-mode:mark-defun-preference' is `containing', `swift-mode:mark-defun'
67+
marks the `outer' function. Likewise, it marks `inner1' if the preference is
68+
`preceding' and `inner2' if the preference is `following'."
69+
:type '(choice (const :tag "Containing" containing)
70+
(const :tag "Preceding" preceding)
71+
(const :tag "Following" following))
72+
:group 'swift
73+
:safe 'symbolp)
74+
3575
(defun swift-mode:beginning-of-defun (&optional arg)
3676
"Move backward to the beginning of a defun.
3777
38-
See `beginning-of-defun' for ARG."
39-
(interactive)
78+
See `beginning-of-defun' for ARG.
79+
80+
Return t if a defun is found. Return nil otherwise.
81+
82+
Push mark at previous position if this is called as a command, not repeatedly,
83+
and the region is not active."
84+
(interactive "p")
4085
(setq arg (or arg 1))
41-
(let (result
42-
pos)
43-
(if (<= 0 arg)
86+
(let ((result t)
87+
(pos (point)))
88+
(if (< 0 arg)
89+
;; Moving backward
4490
(progn
45-
(setq pos (point))
46-
;; Special handling for the case that the cursor is between the
47-
;; beginning of the defun and the open curly brace of the defun.
48-
(when (< (save-excursion
49-
(swift-mode:beginning-of-statement)
50-
(point))
51-
(point))
52-
;; Searches forward { or end of a statement.
53-
(while (not
54-
(memq
55-
(swift-mode:token:type (swift-mode:forward-token-or-list))
56-
'({} implicit-\; \; } outside-of-buffer))))
57-
(when (eq (char-before) ?})
58-
(backward-list))
59-
;; Skips implicit ;
60-
(forward-comment (point-max))
61-
(if (swift-mode:is-point-before-body-of-defun)
62-
(progn
63-
(swift-mode:beginning-of-statement)
64-
(setq result t)
65-
(setq arg (1- arg)))
66-
(goto-char pos)))
67-
(while (< 0 arg)
68-
(setq result (swift-mode:beginning-of-defun-1
69-
#'swift-mode:backward-token-or-list))
70-
(setq arg (1- arg))))
71-
(while (< arg 0)
72-
;; If the cursor is on a defun, ensure the cursor is after the open
73-
;; curly brace of defun.
74-
(setq pos (point))
75-
(swift-mode:beginning-of-statement)
76-
;; swift-mode:beginning-of-statement may forward the cursor if the
77-
;; cursor is on a comment or whitespace. In that case, does not skip
78-
;; the defun.
79-
(when (<= (point) pos)
91+
;; `swift-mode:beginning-of-defun-1' assumes the point is after
92+
;; the open curly brace of defun. So moving the point to just after
93+
;; the open curly brace if the current statement has one.
8094
(while (not
8195
(memq
82-
(swift-mode:token:type (swift-mode:forward-token-or-list))
83-
'({} } outside-of-buffer)))))
96+
(swift-mode:token:type (swift-mode:forward-token))
97+
'({ implicit-\; \; } outside-of-buffer))))
98+
(backward-char)
99+
;; Skips implicit-;
100+
(forward-comment (point-max))
101+
(when (eq (char-after) ?{)
102+
(forward-char))
103+
104+
(setq result (swift-mode:beginning-of-defun-1
105+
#'swift-mode:backward-token-or-list))
106+
(when (and result (< (point) pos))
107+
(setq arg (1- arg)))
84108

109+
(while (and result (< 0 arg))
110+
(setq result (swift-mode:beginning-of-defun-1
111+
#'swift-mode:backward-token-or-list))
112+
(setq arg (1- arg))))
113+
;; Moving forward
114+
(setq result (swift-mode:beginning-of-defun-1
115+
(lambda ()
116+
(prog1 (swift-mode:forward-token-or-list)
117+
(forward-comment (point-max))))))
118+
(when (and result (< pos (point)))
119+
(setq arg (1+ arg)))
120+
(while (and result (< arg 0))
121+
;; Skips the current statement
122+
(while (not
123+
(memq
124+
(swift-mode:token:type (swift-mode:forward-token-or-list))
125+
'({} implicit-\; \; } outside-of-buffer))))
85126
(setq result (swift-mode:beginning-of-defun-1
86127
(lambda ()
87128
(prog1 (swift-mode:forward-token-or-list)
88129
(forward-comment (point-max))))))
89130
(setq arg (1+ arg))))
131+
(and result
132+
(eq this-command 'swift-mode:beginning-of-defun)
133+
(not (eq last-command 'swift-mode:beginning-of-defun))
134+
(not (region-active-p))
135+
(push-mark pos))
90136
result))
91137

92138
(defun swift-mode:beginning-of-defun-1 (next-token-function)
@@ -101,7 +147,6 @@ NEXT-TOKEN-FUNCTION skips the preceding/following token."
101147
(throw 'swift-mode:found-defun t)))
102148
nil))
103149

104-
105150
(defun swift-mode:is-point-before-body-of-defun ()
106151
"Return t it the point is just before the body of a defun.
107152
@@ -132,8 +177,6 @@ Return nil otherwise."
132177
(setq previous-token (swift-mode:backward-token-or-list))
133178
(setq previous-type (swift-mode:token:type previous-token))
134179
(setq previous-text (swift-mode:token:text previous-token)))
135-
(unless (bobp)
136-
(swift-mode:forward-token-simple))
137180
(or (equal previous-text "init")
138181
(member previous-text defun-keywords))))))
139182

@@ -153,7 +196,9 @@ Intended for internal use."
153196
(progn
154197
(forward-comment (- (point)))
155198
(swift-mode:beginning-of-statement))
199+
;; Excludes comments on previous lines.
156200
(goto-char (swift-mode:token:end parent))
201+
(forward-comment (- (point)))
157202
(setq parent (save-excursion (swift-mode:backward-token)))
158203
(forward-comment (point-max))
159204
(swift-mode:goto-non-comment-bol)
@@ -164,22 +209,32 @@ Intended for internal use."
164209
(defun swift-mode:end-of-defun (&optional arg)
165210
"Move forward to the end of a defun.
166211
167-
See `end-of-defun' for ARG."
168-
(interactive)
212+
See `end-of-defun' for ARG.
213+
214+
Return t if a defun is found. Return nil otherwise.
215+
216+
Push mark at previous position if this is called as a command, not repeatedly,
217+
and the region is not active."
218+
(interactive "p")
169219
(setq arg (or arg 1))
170-
(let (result)
220+
(let (result
221+
(pos (point)))
171222
(if (<= 0 arg)
172223
(while (< 0 arg)
173224
(setq result (swift-mode:end-of-defun-1
174-
#'swift-mode:forward-token-or-list
175-
))
225+
#'swift-mode:forward-token-or-list))
176226
(setq arg (1- arg)))
177227
(while (< arg 0)
178228
(setq result (swift-mode:end-of-defun-1
179229
(lambda ()
180230
(prog1 (swift-mode:backward-token-or-list)
181231
(forward-comment (- (point)))))))
182232
(setq arg (1+ arg))))
233+
(and result
234+
(eq this-command 'swift-mode:end-of-defun)
235+
(not (eq last-command 'swift-mode:end-of-defun))
236+
(not (region-active-p))
237+
(push-mark pos))
183238
result))
184239

185240
(defun swift-mode:end-of-defun-1 (next-token-function)
@@ -196,6 +251,156 @@ NEXT-TOKEN-FUNCTION skips the preceding/following token."
196251
(throw 'swift-mode:found-defun t)))
197252
nil))
198253

254+
(defun swift-mode:mark-defun (&optional allow-extend)
255+
"Put mark at the end of defun, point at the beginning of defun.
256+
257+
If the point is between defuns, mark depend on
258+
`swift-mode:mark-defun-preference'.
259+
260+
If ALLOW-EXTEND is non-nil or called interactively, and the command is repeated
261+
or the region is active, mark the following (if the point is before the mark)
262+
or preceding (if the point is after the mark) defun. If that defun has lesser
263+
nesting level, mark the whole outer defun."
264+
(interactive (list t))
265+
(if (and allow-extend
266+
(or
267+
(and (eq last-command this-command) (mark t))
268+
(region-active-p)))
269+
;; Extends region.
270+
(let ((forward-p (<= (point) (mark))))
271+
(set-mark
272+
(save-excursion
273+
(goto-char (mark))
274+
(if forward-p
275+
(swift-mode:end-of-defun)
276+
(swift-mode:beginning-of-defun))
277+
(point)))
278+
;; Marks the whole outer defun if it has lesser nesting level.
279+
(if forward-p
280+
(goto-char (min (point)
281+
(save-excursion
282+
(goto-char (mark))
283+
(swift-mode:beginning-of-defun)
284+
(point))))
285+
(goto-char (max (point)
286+
(save-excursion
287+
(goto-char (mark))
288+
(swift-mode:end-of-defun)
289+
(point))))))
290+
;; Marks new region.
291+
(let ((region
292+
(cond
293+
((eq swift-mode:mark-defun-preference 'containing)
294+
(swift-mode:containing-defun-region))
295+
((eq swift-mode:mark-defun-preference 'preceding)
296+
(swift-mode:preceding-defun-region))
297+
((eq swift-mode:mark-defun-preference 'following)
298+
(swift-mode:following-defun-region)))))
299+
(if region
300+
(progn (push-mark (cdr region) nil t)
301+
(goto-char (car region))
302+
region)
303+
(when (called-interactively-p 'interactive)
304+
(message "No defun found"))
305+
nil))))
306+
307+
(defun swift-mode:following-defun-region ()
308+
"Return cons representing a region of following defun."
309+
(save-excursion
310+
(let* ((end (and (swift-mode:end-of-defun) (point)))
311+
(beginning (and end (swift-mode:beginning-of-defun) (point))))
312+
(and beginning (cons beginning end)))))
313+
314+
(defun swift-mode:preceding-defun-region ()
315+
"Return cons representing a region of preceding defun."
316+
(save-excursion
317+
(let* ((beginning (and (swift-mode:beginning-of-defun) (point)))
318+
(end (and beginning (swift-mode:end-of-defun) (point))))
319+
(and end (cons beginning end)))))
320+
321+
(defun swift-mode:containing-defun-region ()
322+
"Return cons representing a region of containing defun."
323+
(let* ((pos (point))
324+
(region (swift-mode:following-defun-region))
325+
(extended (and region
326+
(swift-mode:extend-defun-region-with-spaces region))))
327+
(cond
328+
((and extended (<= (car extended) pos (cdr extended)))
329+
region)
330+
331+
((progn
332+
(setq region (swift-mode:preceding-defun-region))
333+
(setq extended (swift-mode:extend-defun-region-with-spaces region))
334+
(and extended (<= (car extended) pos (cdr extended))))
335+
region)
336+
337+
(t
338+
(catch 'swift-mode:found-defun
339+
(while (swift-mode:end-of-defun)
340+
(let ((end (point)))
341+
(save-excursion
342+
(swift-mode:beginning-of-defun)
343+
(when (<= (point) pos end)
344+
(throw 'swift-mode:found-defun (cons (point) end))))))
345+
(cons (point-min) (point-max)))))))
346+
347+
(defun swift-mode:extend-defun-region-with-spaces (region)
348+
"Return REGION extended with surrounding spaces."
349+
(let ((beginning (car region))
350+
(end (cdr region)))
351+
(save-excursion
352+
(goto-char beginning)
353+
(skip-syntax-backward " ")
354+
(setq beginning (point)))
355+
(save-excursion
356+
(goto-char end)
357+
(skip-syntax-forward " ")
358+
(setq end (point)))
359+
(cons beginning end)))
360+
361+
(defun swift-mode:narrow-to-defun (&optional include-comments)
362+
"Make text outside current defun invisible.
363+
364+
If the point is between defuns, mark depend on
365+
`swift-mode:mark-defun-preference'.
366+
367+
Preceding comments are included if INCLUDE-COMMENTS is non-nil.
368+
Interactively, the behavior depends on ‘narrow-to-defun-include-comments’."
369+
(interactive (list narrow-to-defun-include-comments))
370+
(let ((restriction (cons (point-min) (point-max)))
371+
region
372+
extended)
373+
(save-excursion
374+
(widen)
375+
376+
(setq region
377+
(cond
378+
((eq swift-mode:mark-defun-preference 'containing)
379+
(swift-mode:containing-defun-region))
380+
((eq swift-mode:mark-defun-preference 'preceding)
381+
(swift-mode:preceding-defun-region))
382+
((eq swift-mode:mark-defun-preference 'following)
383+
(swift-mode:following-defun-region))))
384+
385+
(setq extended
386+
(and region (swift-mode:extend-defun-region-with-spaces region)))
387+
388+
(when (and extended include-comments)
389+
(save-excursion
390+
(goto-char (car extended))
391+
;; Includes comments.
392+
(forward-comment (- (point)))
393+
;; Excludes spaces and line breaks.
394+
(skip-syntax-forward " >")
395+
;; Includes indentation.
396+
(skip-syntax-backward " ")
397+
(setcar extended (point))))
398+
399+
(if extended
400+
(narrow-to-region (car extended) (cdr extended))
401+
(when (called-interactively-p 'interactive)
402+
(message "No defun found"))
403+
(narrow-to-region (car restriction) (cdr restriction))))))
199404

200405
(provide 'swift-mode-beginning-of-defun)
201406

0 commit comments

Comments
 (0)