/
AndyEdits.py
307 lines (285 loc) · 12.3 KB
/
AndyEdits.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
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
import sublime, sublime_plugin
from os import path
PACKAGE_SETTINGS = "AndyEdits.sublime-settings"
if sublime.platform() == "linux":
# Try and load Linux Python2.6 lib. Default path is for Ubuntu.
linux_lib = sublime.load_settings(PACKAGE_SETTINGS).get("linux_python2.6_lib",
"/usr/lib/python2.6/lib-dynload")
if not linux_lib in sys.path and path.exists(linux_lib):
sys.path.append(linux_lib)
ICON = path.pardir + '/AndyEdits/icon' if \
sublime.load_settings(PACKAGE_SETTINGS).get("use_icon", True) else ""
# a small icon to appear in the gutter - defaults to true
# (may interfere with ST-bookmarks)
ICONSCOPE = sublime.load_settings(PACKAGE_SETTINGS).get("icon_scope", "comment")
# affects the colour of the gutter icon and outlining
JUSTDELETED = {}
def showRegion(view, reg):
view.sel().clear()
view.sel().add(reg)
view.show(reg)
def sameView(view_id):
if not view_id: return False
window = sublime.active_window()
view = window.active_view() if window != None else None
return (view is not None and view.id() == view_id)
def adjustEdits(view):
# Add recently edited line to all previous edits,
# also joins consecutive edit lines together.
# Returns: the edited regions or False if there are none.
edited = view.get_regions("edited_rgns") or []
edited_last = view.get_regions("edited_rgn") or []
if not edited and not edited_last:
return False
new_edits = []
if edited_last:
edited.extend(edited_last)
eov = view.size()
for i, r in enumerate(edited):
if i > 0 and r.begin() == prev_end + 1:
# collapse adjoining regions
new_edits.append(sublime.Region(prev_begin, r.end()))
elif r.begin() < eov:
new_edits.append(r)
prev_begin, prev_end = (r.begin(), r.end())
view.add_regions("edited_rgns", new_edits, ICONSCOPE, ICON, \
sublime.HIDDEN | sublime.PERSISTENT)
return view.get_regions("edited_rgns") or []
def getEditList(view, edited):
the_edits = []
for i, r in enumerate(edited):
curr_line, _ = view.rowcol(r.begin())
curr_text = view.substr(r).strip()[:40]
if not len(curr_text):
curr_text = view.substr(view.line(r)).strip()[:40] + " (line)"
the_edits.append("Line: %03d %s" % ( curr_line + 1, curr_text ))
return the_edits
def getFullEditList(view, edited):
the_edits = []
locations = []
for i, r in enumerate(edited):
curr_line, _ = view.rowcol(r.begin())
curr_text = view.substr(r).strip()[:40]
if not len(curr_text):
curr_text = view.substr(view.line(r)).strip()[:40] + " (line)"
the_edits.append(" Line: %03d %s" % ( curr_line + 1, curr_text ))
locations.append((view, r))
return the_edits, locations
class ListAllEdits(sublime_plugin.WindowCommand):
def run(self):
adjustEdits(self.window.active_view())
full_list = []
self.locations = []
for vw in self.window.views():
edited = vw.get_regions("edited_rgns") or []
if edited:
the_edits, locs = getFullEditList(vw, edited)
if the_edits:
the_edits.insert(0, "%s" % (vw.file_name() or "No filename"))
locs.insert(0, (vw, vw.sel()[0]))
full_list += the_edits
self.locations += locs
if full_list:
self.window.show_quick_panel(full_list, self.on_chosen)
else:
sublime.status_message('No edits to list.')
def on_chosen(self, index):
if index == -1: return
vw, reg = self.locations[index]
sublime.active_window().focus_view(vw)
showRegion(vw, reg)
del self.locations[:]
class ToggleEditsCommand(sublime_plugin.TextCommand):
# Toggles outlining of edited lines.
def run(self, edit):
if not sameView(self.view.id()):
sublime.status_message('Click into the view/tab first.')
return
edited = adjustEdits(self.view)
if not edited:
sublime.status_message('No edits to show or hide.')
return
toggled = self.view.get_regions("toggled_edits") or []
if toggled:
self.view.erase_regions("toggled_edits")
else:
self.view.add_regions("toggled_edits", edited, ICONSCOPE, \
ICON, sublime.DRAW_OUTLINED)
sublime.status_message("There are %d edit regions." % (len(edited)))
class PrevEditLineCommand(sublime_plugin.TextCommand):
def run(self, edit):
if not sameView(self.view.id()):
sublime.status_message('Click into the view/tab first.')
return
currA = self.view.sel()[0].begin()
edited = adjustEdits(self.view)
if not edited:
sublime.status_message('No edits to go to.')
return
for reg in [r for r in reversed(edited) if r.begin() < currA]:
showRegion(self.view, reg)
break
else:
sublime.status_message('No edits further up.')
class NextEditLineCommand(sublime_plugin.TextCommand):
def run(self, edit):
if not sameView(self.view.id()):
sublime.status_message('Click into the view/tab first.')
return
currA = self.view.sel()[0].begin()
edited = adjustEdits(self.view)
if not edited:
sublime.status_message('No edits to go to.')
return
for reg in [r for r in edited if r.begin() > currA]:
showRegion(self.view, reg)
break
else:
sublime.status_message('No edits further down.')
class CreateEditCommand(sublime_plugin.TextCommand):
# Create an edit region for the current selection.
def run(self, edit):
if not sameView(self.view.id()):
sublime.status_message('Click into the view/tab first.')
return
edited = adjustEdits(self.view) or []
curr_region = self.view.sel()[0]
if curr_region.empty():
sublime.status_message('You must select some text.')
return
edited.append(curr_region)
self.view.add_regions("edited_rgns", edited, ICONSCOPE, ICON, \
sublime.HIDDEN | sublime.PERSISTENT)
sublime.status_message('New edit region created.')
class QuickEditsCommand(sublime_plugin.TextCommand):
# Shows a quick panel to jump to edit lines.
def run(self, edit):
self.vid = self.view.id()
if not sameView(self.vid):
sublime.status_message('Click into the view/tab first.')
return
edited = adjustEdits(self.view)
if not edited:
sublime.status_message('No edits to list.')
return
the_edits = getEditList(self.view, edited)
if the_edits:
sublime.active_window().show_quick_panel(the_edits, self.on_chosen)
def on_chosen(self, index):
if index == -1: return
if not sameView(self.vid):
sublime.status_message('You are in a different view.')
return
edited = self.view.get_regions("edited_rgns") or []
for reg in [r for i, r in enumerate(edited) if i == index]:
showRegion(self.view, reg)
break
class DeleteEditCommand(sublime_plugin.TextCommand):
# Shows a quick panel to remove edit history for a region.
def run(self, edit):
self.vid = self.view.id()
if not sameView(self.vid):
sublime.status_message('Click into the view/tab first.')
return
edited = adjustEdits(self.view)
if not edited:
sublime.status_message('No edit history to delete.')
return
the_edits = getEditList(self.view, edited)
if the_edits:
sublime.status_message('Cannot delete the most recent edit.')
sublime.active_window().show_quick_panel(the_edits, self.on_chosen)
def removeTempHighlight(self, old_line):
self.view.erase_regions("temp_del")
sublime.status_message("Edit history removed from line %d." % old_line)
def on_chosen(self, index):
if index == -1: return
if not sameView(self.vid):
sublime.status_message('You are in a different view.')
return
edited = self.view.get_regions("edited_rgns") or []
reg = edited[index]
current_editr = self.view.get_regions("edited_rgn") or []
if current_editr and reg.intersects(current_editr[0]):
sublime.status_message('Cannot delete most recent edit.')
return
del edited[index]
self.view.add_regions("edited_rgns", edited, ICONSCOPE, ICON, \
sublime.HIDDEN | sublime.PERSISTENT)
is_toggled = self.view.get_regions("toggled_edits") or []
if is_toggled:
self.view.erase_regions("toggled_edits")
sublime.active_window().run_command("toggle_edits")
old_line, _ = self.view.rowcol(reg.begin())
self.view.add_regions("temp_del", [reg], "invalid", sublime.DRAW_OUTLINED)
sublime.set_timeout(lambda: self.removeTempHighlight(old_line + 1), 500)
JUSTDELETED[self.vid] = True
class CaptureEditing(sublime_plugin.EventListener):
edit_info = {}
def on_modified(self, view):
# Create hidden regions that mirror the edited regions.
# Maintains a single edit region for the current line.
if not sameView(view.id()):
# maybe using Find? etc.
window = sublime.active_window()
edit_view = window.active_view() if window != None else None
_ = adjustEdits(edit_view)
return
vid = view.id()
if not CaptureEditing.edit_info.has_key(vid):
CaptureEditing.edit_info[vid] = {}
cview = CaptureEditing.edit_info[vid]
sel = view.sel()[0]
currA, currB = (sel.begin(), sel.end())
cview['curr_line'], _ = view.rowcol(currA)
if not cview.has_key('prev_line') or cview['prev_line'] is None:
# on first run, or just deleted an edit region
cview['prev_line'] = cview['curr_line']
if currA > 0 and sel.empty():
# include the first character?
same_line, _ = view.rowcol(currA - 1)
if cview['curr_line'] == same_line:
currA -= 1
cview['lastx'], cview['lasty'] = (currA, currB)
elif cview['curr_line'] == cview['prev_line']:
# still on the same line
cview['lastx'] = min(currA, cview['lastx'])
# don't go beyond end of current line..
cview['lasty'] = max(currB, min(cview['lasty'], view.line(sel).end()))
else:
# moving to a different line
cview['prev_line'] = cview['curr_line']
if currA > 0 and sel.empty():
# include the first character?
same_line, _ = view.rowcol(currA - 1)
if cview['curr_line'] == same_line:
currA -= 1
cview['lastx'], cview['lasty'] = (currA, currB)
_ = adjustEdits(view)
curr_edit = sublime.Region(cview['lastx'], cview['lasty'])
view.add_regions("edited_rgn", [curr_edit], ICONSCOPE, \
ICON, sublime.HIDDEN | sublime.PERSISTENT)
def on_selection_modified(self, view):
vid = view.id()
if not CaptureEditing.edit_info.has_key(vid):
CaptureEditing.edit_info[vid] = {}
cview = CaptureEditing.edit_info[vid]
if JUSTDELETED.has_key(view.id()) and JUSTDELETED[view.id()] == True:
JUSTDELETED[view.id()] = False
cview['prev_line'] = None
return
if cview.has_key('prev_line') and cview['prev_line'] is not None:
curr_line, _ = view.rowcol(view.sel()[0].begin())
if cview['prev_line'] != curr_line:
edited = view.get_regions('edited_rgns') or []
if edited:
found_reg = False
prev_reg = sublime.Region(cview['lastx'], cview['lasty'])
for i, r in enumerate(edited):
if r.contains(prev_reg):
found_reg = True
break
if not found_reg:
edited.append(prev_reg)
view.add_regions("edited_rgns", edited, ICONSCOPE, \
ICON, sublime.HIDDEN | sublime.PERSISTENT)