-
Notifications
You must be signed in to change notification settings - Fork 69
/
s_expressions.clj
79 lines (69 loc) · 3 KB
/
s_expressions.clj
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
(ns refactor-nrepl.s-expressions
(:require [rewrite-clj.parser :as zip-parser]
[rewrite-clj.reader :as zip-reader]
[rewrite-clj.zip :as zip]))
(defn all-zlocs
"Generate a seq of all zlocs in a depth-first manner"
[zipper]
(take-while (complement zip/end?) (iterate zip/next zipper)))
(defn- comment-or-string-or-uneval-or-nil? [zloc]
(or (nil? zloc)
(= :uneval (zip/tag zloc))
(not (zip/sexpr zloc)) ; comment node
(string? (zip/sexpr zloc))))
(defn get-first-sexp
^String [file-content]
(let [reader (zip-reader/string-reader file-content)]
(loop [sexp (zip-parser/parse reader)]
(let [zloc (zip/edn sexp)]
(if (and zloc (not (comment-or-string-or-uneval-or-nil? zloc)))
(zip/string zloc)
(when (.peek-char reader)
(recur (zip-parser/parse reader))))))))
(defn get-last-sexp
^String [file-content]
(let [zloc (->> file-content zip/of-string zip/rightmost)]
(some (fn [zloc] (when-not (comment-or-string-or-uneval-or-nil? zloc)
(zip/string zloc)))
(take-while (complement nil?) (iterate zip/left zloc)))))
(defn- node-at-loc?
"True if node encloses point defined by `loc-line` and `loc-column`."
[zloc ^long loc-line ^long loc-column]
(let [[line end-line column end-column] (->> (zip/node zloc)
meta
((juxt :row :end-row :col :end-col))
(map (comp dec long)))]
(or (< line loc-line end-line)
(and (or (= line loc-line)
(= end-line loc-line))
(<= column loc-column end-column)))))
(defn- zip-to
"Move the zipper to the node at `loc-line` and `loc-col`.
Implementation uses `all-zlocs` and exploits the fact that it generates
a seq of nodes in depth-first order."
[zipper ^long loc-line ^long loc-column]
(reduce
(fn [node-at-loc zloc]
(if (node-at-loc? zloc loc-line loc-column) zloc node-at-loc))
zipper
(all-zlocs zipper)))
(defn get-enclosing-sexp
"Extracts the sexp enclosing point at LINE and COLUMN in FILE-CONTENT,
and optionally LEVEL.
A string is not treated as a sexp by this function. If LEVEL is
provided finds the enclosing sexp up to level. LEVEL defaults to 1
for the immediate enclosing sexp.
Both line and column are indexed from 0."
([file-content line column]
(get-enclosing-sexp file-content line column 1))
([file-content ^long line ^long column ^long level]
(let [zloc (zip-to (zip/of-string file-content) line column)
zloc (nth (iterate zip/up zloc) (dec level))]
(cond
(and zloc (string? (zip/sexpr zloc))) (zip/string (zip/up zloc))
(and zloc (seq? (zip/sexpr zloc))) (zip/string zloc)
zloc (zip/string (zip/up zloc))
:else (throw (ex-info "Can't find sexp boundary"
{:file-content file-content
:line line
:column column}))))))