This repository has been archived by the owner on Apr 11, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
js_var_shortcuts.py
228 lines (185 loc) · 8.36 KB
/
js_var_shortcuts.py
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
# Load in core dependencies
import json
import os
import re
import subprocess
import sublime
import sublime_plugin
import tempfile
# Localize Region
Region = sublime.Region
# Set up constants
__dir__ = os.path.dirname(os.path.abspath(__file__))
# Define a custom RegionSet (cannot use sublime's =_=)
class RegionSet():
def __init__(self):
self.regions = set()
def add(self, region):
self.regions.add(region)
def contains(self, needle):
contains = False
for haystack in self.regions:
if haystack.contains(needle):
contains = True
return contains
# Define a helper for adding linked list refs
def linked_listify(items):
# Bind head to tail
prev_item = None
for item in items:
item['prev'] = prev_item
prev_item = item
# Get tail item and bind in reverse
last_item = item
while last_item:
item = last_item['prev']
if item:
item['next'] = last_item
last_item = item
else:
last_item = None
# Define a deletion command
class JsVarDeleteCommand(sublime_plugin.TextCommand):
def run(self, edit):
# If the view is not JavaScript, perform the default
view = self.view
if view.settings().get('syntax') != u'Packages/JavaScript/JavaScript.tmLanguage':
return self.run_default()
# Write to a temporary fie
(i, filepath) = tempfile.mkstemp()
f = open(filepath, 'w')
script = view.substr(Region(0, view.size()))
f.write(script)
f.close()
# Get the var locations via esprima (JS AST parser)
child = subprocess.Popen(["node", "--eval", """
var fs = require('fs'),
varFind = require('var-find'),
filepath = process.argv[1],
script = fs.readFileSync(filepath, 'utf8'),
varGroups = varFind(script);
console.log(JSON.stringify(varGroups));
""", filepath], cwd=__dir__, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
var_group_json = child.stdout.read()
var_group_stderr = child.stderr.read()
# Kill the child
child.kill()
# If there is stderr, perform default action and throw stderr
if var_group_stderr:
self.run_default()
raise Exception(var_group_stderr)
# Interpret the variable groups
var_groups = json.loads(var_group_json)
# TODO: If any var_groups are adjacent via whitespace, join them (handles multi-var-wide case)
# TODO: The technicality being... how we delete things multiple `var`s
# Collect all of the variable regions
var_regions = RegionSet()
for group in var_groups:
# Generate and save a region to our RegionSet
region = Region(group['start'], group['end'])
var_regions.add(region)
# Save the region to the group for later
group['region'] = region
# If none of the selections are not in a variable region, perform the default behavior
selection = view.sel()
in_var_region = map(lambda sel_region: var_regions.contains(sel_region), selection)
if not any(in_var_region):
self.run_default()
# Otherwise, if all of the selections are in a variable region
elif all(in_var_region):
# TODO: Optimization: When a var is marked, skip all remaining indicies contained.
# TODO: Optimization: Each loop, check that all vars are marked. If they are, exit it.
# TODO: Break this down...
# Generate a collection for each selection region
for group in var_groups:
group['selections'] = []
# Map each selection to its group
for sel in selection:
for group in var_groups:
if group['region'].contains(sel):
group['selections'].append(sel)
# Create placeholder for deletion actions
delete_regions = []
# Sort the groups into ascending order
var_groups.sort(lambda a, b: a['start'] - b['start'])
# Add ['prev'] and ['next'] properties for each group
linked_listify(var_groups)
# Iterate over the groups
for group in var_groups:
# Sort and link the variables
vars = group['vars']
vars.sort(lambda a, b: a['start'] - b['start'])
linked_listify(vars)
# Grab the first and last var
first_var = vars[0]
last_var = vars[len(vars) - 1]
# Break down the selection into an ordered list of indicies
selected_indicies = []
for sel in group['selections']:
selected_indicies += range(sel.begin(), sel.end() + 1)
selected_indicies.sort()
# Iterate over the indicies
for index in selected_indicies:
# TODO: Optimization: Binary search for lowest starting index (including 0)
# If we are before the first var, select the first var
# var| abc, def;
if index < first_var['start']:
first_var['matched'] = True
continue
# If we are after the last var, select it
# var abc, def|;
if index > last_var['end']:
last_var['matched'] = True
continue
# Walk the vars
for var in vars:
# If we are in the var
if index >= var['start'] and index <= var['end']:
var['matched'] = True
break
# Otherwise, if we are between this var and the next one
# DEV: var['next'] will be defined because we checked being after last_var['end']
elif index > var['end'] and index < var['next']['start']:
# If we are before the separating comma, assume next var
# var abc|, def;
pattern = re.compile('\s+')
next_nonwhitespace = pattern.search(script, var['end']).end(0)
if next_nonwhitespace != var['next']['start']:
var['matched'] = True
break
# Otherwise, assume next var
# var abc,| def;
else:
var['next']['matched'] = True
break
# If every var was matched, delete the group
every_var_matched = all(map(lambda var: var.get('matched', False), vars))
if every_var_matched:
delete_regions.append(group['region'])
# Otherwise
else:
# Walk the vars
break_encountered = False
for var in vars:
# If we are in a break, mark it
if not var.get('matched', False):
break_encountered = True
continue
# If we are before the break, buffer on the right
# var [^abc , ]def , ghi;
if not break_encountered:
var_end = var['end']
pattern = re.compile('\s+')
buffered_end = pattern.search(script, var_end).end(0)
delete_regions.append(Region(var['start'], buffered_end))
# Otherwise, (we are after the break), buffer on the left
# var abc , def[ , ^ghi];
else:
delete_regions.append(Region(var['prev']['end'], var['end']))
# Reverse sort the deleted groups
delete_regions.sort(lambda a, b: b.begin() - a.begin())
# Delete them all @_@
for region in delete_regions:
view.erase(edit, region)
def run_default(self):
self.view.run_command("delete_word", {"forward": False})