This repository has been archived by the owner on May 10, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 50
/
editor-utils.coffee
219 lines (196 loc) · 8.52 KB
/
editor-utils.coffee
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
{CompositeDisposable, Range, Point} = require 'atom'
edn_reader = require './proto_repl/edn_reader.js'
module.exports = EditorUtils =
# Escapes the Clojure code and places it in quoatations
escapeClojureCodeInString: (code)->
escaped = code.replace(/\\/g,"\\\\").replace(/"/g, "\\\"")
"\"#{escaped}\""
# Finds a Clojure Namespace declaration in the editor and returns the name
# of the namespace.
findNsDeclaration: (editor)->
for range in @getTopLevelRanges(editor)
txt = editor.getTextInBufferRange(range)
try
return edn_reader.parse(txt)[1] if txt.match(/^\(ns /)
catch e
return null
# Returns true if the position in the text editor is in a Markdown file in a
# code block that contains Clojure.
isPosInClojureMarkdown: (editor, pos)->
scopeDesc = editor.scopeDescriptorForBufferPosition(pos)
scopeDesc.scopes.indexOf("markup.code.clojure.gfm") >= 0
# Finds a starting markdown section like "```clojure" searching backwards
# from fromPos.
findMarkdownCodeBlockStartPosition: (editor, fromPos) ->
startPos = null
# We translate the search range forward in case the cursor is in the middle
# of the declaration of the markdown block.
scanRange = new Range([0,0], fromPos.translate(new Point(0, 10)))
editor.backwardsScanInBufferRange /```clojure/ig, scanRange, (result) ->
startPos = result.range.start.translate(new Point(1,0))
result.stop()
startPos
# Finds a closing markdown section "```" searching forwards from fromPos.
findMarkdownCodeBlockEndPosition: (editor, fromPos) ->
endPos = null
scanRange = new Range(fromPos, editor.buffer.getEndPosition())
editor.scanInBufferRange /```/g, scanRange, (result) ->
endPos = result.range.start
result.stop()
endPos
# Takes an editor and the position of a brace found in scanning and Determines
# if the brace found at that position can be ignored. If the brace is in a
# comment or inside a string it can be ignored.
isIgnorableBrace: (editor, pos)->
scopes = editor.scopeDescriptorForBufferPosition(pos).scopes
scopes.indexOf("string.quoted.double.clojure") >= 0 ||
scopes.indexOf("comment.line.semicolon.clojure") >= 0 ||
scopes.indexOf("string.regexp.clojure") >= 0
findBlockStartPosition: (editor, fromPos) ->
braceClosed =
"}": 0
")": 0
"]": 0
openToClose =
"{": "}"
"[": "]"
"(": ")"
startPos = null
editor.backwardsScanInBufferRange /[\{\}\[\]\(\)]/g, new Range([0,0], fromPos), (result) =>
if !(@isIgnorableBrace(editor, result.range.start))
c = ""+result.match[0]
if braceClosed[c] != undefined
braceClosed[c]++
else
braceClosed[openToClose[c]]--
if braceClosed[openToClose[c]] == -1
startPos = result.range.start
result.stop()
startPos
findBlockEndPosition: (editor, fromPos) ->
braceOpened =
"{": 0
"(": 0
"[": 0
closeToOpen =
"}": "{"
"]": "["
")": "("
endPos = null
scanRange = new Range(fromPos, editor.buffer.getEndPosition())
editor.scanInBufferRange /[\{\}\[\]\(\)]/g, scanRange, (result) =>
if !(@isIgnorableBrace(editor, result.range.start))
c = ""+result.match[0]
if braceOpened[c] != undefined
braceOpened[c]++
else
braceOpened[closeToOpen[c]]--
if braceOpened[closeToOpen[c]] == -1
endPos = result.range.start
result.stop()
endPos
# Determines if the cursor is directly after a closed block. If it is returns
# the text of that block
directlyAfterBlockRange: (editor)->
pos = editor.getCursorBufferPosition()
if pos.column > 0
previousPos = new Point(pos.row, pos.column-1)
previousChar = editor.getTextInBufferRange(new Range(previousPos, pos))
if [")","}","]"].indexOf(previousChar) >= 0
if startPos = @findBlockStartPosition(editor, previousPos)
new Range(startPos, pos)
# Determines if the cursor is directly before a block opening. If it is returns
# the text of that block
directlyBeforeBlockRange: (editor)->
pos = editor.getCursorBufferPosition()
subsequentPos = pos.translate(new Point(0, 1))
afterChar = editor.getTextInBufferRange(new Range(pos, subsequentPos))
if ["(","{","["].indexOf(afterChar) >= 0
if endPos = @findBlockEndPosition(editor, subsequentPos)
closingPos = endPos.translate(new Point(0, 1))
new Range(pos, closingPos)
getCursorInClojureBlockRange: (editor)->
if range = @directlyAfterBlockRange(editor)
range
else if range = @directlyBeforeBlockRange(editor)
range
else
pos = editor.getCursorBufferPosition()
startPos = @findBlockStartPosition(editor, pos)
endPos = @findBlockEndPosition(editor, pos)
if startPos && endPos
closingPos = endPos.translate(new Point(0, 1))
# Note that this will if used instead of executing the code implement expand selection on
# repeated executions
#editor.setSelectedBufferRange(new Range(startPos, closingPos))
new Range(startPos, closingPos)
getCursorInMarkdownBlockRange: (editor)->
pos = editor.getCursorBufferPosition()
if @isPosInClojureMarkdown(editor, pos)
if startPos = @findMarkdownCodeBlockStartPosition(editor, pos)
# It's safer to search from the start of the startPos instead of searching from the end position
if endPos = @findMarkdownCodeBlockEndPosition(editor, startPos)
new Range(startPos, endPos)
# If the cursor is located in a Clojure block (in parentheses, brackets, or
# braces) or next to one returns the text of that block. Also works with
# Markdown blocks of code starting with ```clojure and ending with ```.
getCursorInBlockRange: (editor, {topLevel} = {topLevel: false})->
if topLevel and range = @getCursorInClojureTopBlockRange(editor,{lookInComments: true})
range
else if range = @getCursorInClojureBlockRange(editor)
range
else
@getCursorInMarkdownBlockRange(editor)
# Constructs a list of `Range`s, one for each top level form.
getTopLevelRanges: (editor, {lookInComments}={lookInComments: false}) ->
ranges = []
braceOpened = 0
inTopLevelComment = false
if lookInComments
rex = /(\(comment\s|[\{\}\[\]\(\)])/g
else
rex = /[\{\}\[\]\(\)]/g
editor.scan rex, (result) =>
if !(@isIgnorableBrace(editor, result.range.start))
matchesComment = result.matchText.match(/^\(comment\s/)
if matchesComment and braceOpened == 0
inTopLevelComment = true
c = ""+result.match[0]
if ["(","{","["].indexOf(c) >= 0 or matchesComment
if (braceOpened == 1 and inTopLevelComment == true) or
(braceOpened == 0 and inTopLevelComment == false)
ranges.push([result.range.start])
braceOpened++
else if [")","}","]"].indexOf(c) >= 0
braceOpened--
if (braceOpened == 1 and inTopLevelComment == true) or
(braceOpened == 0 and inTopLevelComment == false)
ranges[ranges.length - 1].push(result.range.end)
if braceOpened == 0 and inTopLevelComment == true
inTopLevelComment = false
ranges
.filter((range) -> range.length == 2)
.map((range) -> Range.fromObject(range))
# Returns the `Range` that corresponds to the top level form that contains the current cursor position.
# Doesn't work in Markdown blocks of code.
getCursorInClojureTopBlockRange: (editor,options={})->
pos = editor.getCursorBufferPosition()
topLevelRanges = @getTopLevelRanges(editor,options)
topLevelRanges.find (range) -> range.containsPoint(pos)
# Searches all open text editors for the given string. Returns a tuple of the
# editor found and the range within the editor for the first location that
# matches
findEditorRangeContainingString: (str)->
editors = atom.workspace.getTextEditors()
# Create a literal regex to search for the given str
# From http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
regex = new RegExp(str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'))
for editor in editors
foundRange = null
editor.scan regex, (matched)=>
foundRange = matched.range
matched.stop()
return [editor, foundRange] if foundRange
# Determines if a cursor is within a range of text of a var and returns the text
getClojureVarUnderCursor: (editor)->
editor.getWordUnderCursor wordRegex: /[a-zA-Z0-9\-.$!?\/><*=_]+/