Sublime Forum

Split/iterate [regions]

#20

Wow, that didn’t take long! Consecutive edited-lines will be treated as one edit:

[code]import sublime, sublime_plugin

def AdjustEdits(view):
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 = ]
edited.extend(edited_last)
for i, r in enumerate(edited):
if i > 0 and r.begin() == prev_end:
new_edits.append(sublime.Region(prev_begin, r.end()))
else:
new_edits.append®
prev_begin, prev_end = (r.begin(), r.end())

view.add_regions("edited_rgns", new_edits, "edits", sublime.HIDDEN)
view.erase_regions("edited_rgn")
return view.get_regions("edited_rgns") or ]

class PrevEditLineCommand(sublime_plugin.TextCommand):
def run(self, edit):
vid = self.view.id()
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]:
self.view.sel().clear()
self.view.show(reg)
self.view.sel().add(reg)
break
else:
sublime.status_message(‘No edits further up.’)

class NextEditLineCommand(sublime_plugin.TextCommand):
def run(self, edit):
vid = self.view.id()
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]:
self.view.sel().clear()
self.view.show(reg)
self.view.sel().add(reg)
break
else:
sublime.status_message(‘No edits further down.’)

class QuickEditsCommand(sublime_plugin.TextCommand):
def run(self, edit):
window = sublime.active_window()
view = window.active_view() if window != None else None
if view is None or view.id() != self.view.id():
sublime.status_message(‘Click into the view/tab first.’)
return
self.vid = self.view.id()
edited = AdjustEdits(self.view)
if not edited:
sublime.status_message(‘No edits to list.’)
return
the_edits = ]
for i, r in enumerate(edited):
curr_line, _ = self.view.rowcol(r.begin())
curr_text = self.view.substr®.strip()
if not len(curr_text):
curr_text = self.view.substr(self.view.line®).strip():40]
+ " (line)"
the_edits.append(“Line: %03d %s” % ( curr_line + 1, curr_text ))
window.show_quick_panel(the_edits, self.on_chosen)

def on_chosen(self, index):
    if index == -1: return
    window = sublime.active_window()
    view = window.active_view() if window != None else None
    if view is None or view.id() != 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]:
        self.view.sel().clear()
        self.view.show(reg)
        self.view.sel().add(reg)
        break

class CaptureEditing(sublime_plugin.EventListener):
def on_modified(self, view):
# create hidden regions that mirror the edited regions
vid = view.id()
sel = view.sel()[0]
currA, currB = (sel.begin(), sel.end())
self.curr_line, _ = view.rowcol(currA)
if not hasattr(self, ‘prev_line’):
self.prev_line = self.curr_line
if currA > 0 and sel.empty():
currA -= 1
self.lastx, self.lasty = (currA, currB)
self.curr_edit = sublime.Region(self.lastx, self.lasty)
view.add_regions(“edited_rgn”,[self.curr_edit], “edits”, sublime.HIDDEN)
return
if self.curr_line == self.prev_line:
self.lastx = min(currA, self.lastx)
self.lasty = max(currB, self.lasty)
self.curr_edit = sublime.Region(self.lastx, self.lasty)
view.add_regions(“edited_rgn”,[self.curr_edit], “edits”, sublime.HIDDEN)
else:
self.prev_line = self.curr_line
if currA > 0 and sel.empty():
currA -= 1
self.lastx, self.lasty = (currA, currB)
_ = AdjustEdits(view)[/code]

0 Likes

#21

Finished! I can toggle the display of the edited regions (Ctrl+Alt+H) and they are persistent. That is, if you close the application then they will persist, but not if you close the file/view (this is how persistence behaves by default in ST).

{ "keys": "ctrl+alt+h"], "command": "toggle_edits" }, { "keys": "ctrl+alt+j"], "command": "quick_edits" }, { "keys": "ctrl+alt+k"], "command": "prev_edit_line" }, { "keys": "ctrl+alt+l"], "command": "next_edit_line" }

[code]import sublime, sublime_plugin

def AdjustEdits(view):
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 = ]
edited.extend(edited_last)
for i, r in enumerate(edited):
if i > 0 and r.begin() == prev_end:
new_edits.append(sublime.Region(prev_begin, r.end()))
else:
new_edits.append®
prev_begin, prev_end = (r.begin(), r.end())

view.add_regions("edited_rgns", new_edits, "edits", \
    sublime.HIDDEN | sublime.PERSISTENT)
view.erase_regions("edited_rgn")
return view.get_regions("edited_rgns") or ]

class ToggleEditsCommand(sublime_plugin.TextCommand):
def run(self, edit):
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, “keyword”,
sublime.DRAW_OUTLINED)

class PrevEditLineCommand(sublime_plugin.TextCommand):
def run(self, edit):
vid = self.view.id()
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]:
self.view.sel().clear()
self.view.show(reg)
self.view.sel().add(reg)
break
else:
sublime.status_message(‘No edits further up.’)

class NextEditLineCommand(sublime_plugin.TextCommand):
def run(self, edit):
vid = self.view.id()
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]:
self.view.sel().clear()
self.view.show(reg)
self.view.sel().add(reg)
break
else:
sublime.status_message(‘No edits further down.’)

class QuickEditsCommand(sublime_plugin.TextCommand):
def run(self, edit):
window = sublime.active_window()
view = window.active_view() if window != None else None
if view is None or view.id() != self.view.id():
sublime.status_message(‘Click into the view/tab first.’)
return
self.vid = self.view.id()
edited = AdjustEdits(self.view)
if not edited:
sublime.status_message(‘No edits to list.’)
return
the_edits = ]
for i, r in enumerate(edited):
curr_line, _ = self.view.rowcol(r.begin())
curr_text = self.view.substr®.strip():40]
if not len(curr_text):
curr_text = self.view.substr(self.view.line®).strip():40]
+ " (line)"
the_edits.append(“Line: %03d %s” % ( curr_line + 1, curr_text ))
window.show_quick_panel(the_edits, self.on_chosen)

def on_chosen(self, index):
    if index == -1: return
    window = sublime.active_window()
    view = window.active_view() if window != None else None
    if view is None or view.id() != 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]:
        self.view.sel().clear()
        self.view.show(reg)
        self.view.sel().add(reg)
        break

class CaptureEditing(sublime_plugin.EventListener):
def on_modified(self, view):
# create hidden regions that mirror the edited regions
vid = view.id()
sel = view.sel()[0]
currA, currB = (sel.begin(), sel.end())
self.curr_line, _ = view.rowcol(currA)
if not hasattr(self, ‘prev_line’):
self.prev_line = self.curr_line
if currA > 0 and sel.empty():
currA -= 1
self.lastx, self.lasty = (currA, currB)
self.curr_edit = sublime.Region(self.lastx, self.lasty)
view.add_regions(“edited_rgn”,[self.curr_edit], “edits”,
sublime.HIDDEN | sublime.PERSISTENT)
return
if self.curr_line == self.prev_line:
self.lastx = min(currA, self.lastx)
self.lasty = max(currB, self.lasty)
self.curr_edit = sublime.Region(self.lastx, self.lasty)
view.add_regions(“edited_rgn”,[self.curr_edit], “edits”,
sublime.HIDDEN | sublime.PERSISTENT)
else:
self.prev_line = self.curr_line
if currA > 0 and sel.empty():
currA -= 1
self.lastx, self.lasty = (currA, currB)
_ = AdjustEdits(view)[/code]
Not bad for a few hours work - I might actually use this :laughing:
AndyEdits.zip (1.28 KB)

0 Likes

#22

Talking of JavaScript prototypes…

Date.prototype.format = function (sFormat, twelve) { // Returns: A string version of the date. // Usage: date_instance.format("d mmm yy hh:nn:ss ap") or // date_instance.format("dddd dd mmmm hh:nn", true) // Defaults to YYYY/MM/DD. // twelve == true for a 12hr clock, or just AP or ap within // sFormat (for AM/PM or am/pm). // Use z or zzz for milliseconds and xx for suffixes (st, nd, etc.). var MonthNames = "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; var DayNames = "Sunday", "Monday", "Tueday", "Wednesday", "Thursday", "Friday", "Saturday" ]; var dDate = this || new Date(), D = dDate.getDate(), DDDD = DayNames[dDate.getDay()], DDD = DDDD.substr(0,3), M = dDate.getMonth()+1, MMMM = MonthNames[dDate.getMonth()], MMM = MMMM.substr(0,3), YYYY = dDate.getFullYear(), YY = ('' + YYYY).substr(2, 2), H = dDate.getHours(), N = dDate.getMinutes(), S = dDate.getSeconds(), Z = dDate.getMilliseconds(), ap = (H > 11) ? "pm" : "am", // pad with leading zeros, if required DD = ( D < 10 ? "0" : "" ) + D, MM = ( M < 10 ? "0" : "" ) + M, NN = ( N < 10 ? "0" : "" ) + N, SS = ( S < 10 ? "0" : "" ) + S, ZZZ = ( Z < 10 ? "00" : (Z < 100 ? "0" : "") ) + Z, XX; var AP = (sFormat && (sFormat.toUpperCase().indexOf('AP')+1)) ? ((sFormat.indexOf('ap')+1) ? ap : ap.toUpperCase()) : ''; if (twelve || AP) { H = (H < 12) ? (H || 12) : ((H - 12) || 12); } var HH = ( H < 10 ? "0" : "" ) + H; XX = (D == 1 || D == 21 || D == 31) ? "st" : ((D == 2 || D == 22) ? "nd" : ((D == 3 || D == 23) ? "rd" : "th")); sFormat = ( sFormat ) ? sFormat.toUpperCase() : 'YYYY/MM/DD'; var sParsed = sFormat.replace(/D{1,4}|M{1,4}|Y{2,4}|H{1,2}|N{1,2}|S{1,2}|Z{1,3}|XX|AP/g, function (m) { try { return eval(m); } catch (e) { return ''; } }); return sParsed; };

I appreciate the arguments about JS prototypes, but this is one exception I’m happy to use. I suppose it could be named **andyFormat ** :sunglasses:
[There are other versions than mine, but mine distinguishes between [b]AM and **am **and enables **suffixes **1st ,2nd etc…]

0 Likes

#23

Attached screenshot.


0 Likes

#24

Second/last screenshot.


0 Likes

#25

Sorry, but I should offer the following version which prevents anything happening if you are not in a View; that is, if you are in the Find dialog, or anywhere else.

[code]import sublime, sublime_plugin

def adjustEdits(view):
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 = ]
edited.extend(edited_last)
for i, r in enumerate(edited):
if i > 0 and r.begin() == prev_end:
new_edits.append(sublime.Region(prev_begin, r.end()))
else:
new_edits.append®
prev_begin, prev_end = (r.begin(), r.end())

view.add_regions("edited_rgns", new_edits, "keyword", \
    sublime.HIDDEN | sublime.PERSISTENT)
view.erase_regions("edited_rgn")
return view.get_regions("edited_rgns") or ]

def showRegion(view, reg):
view.sel().clear()
view.show(reg)
view.sel().add(reg)

class ToggleEditsCommand(sublime_plugin.TextCommand):
def run(self, edit):
window = sublime.active_window()
view = window.active_view() if window != None else None
if view is None or view.id() != 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,
“keyword”, sublime.DRAW_OUTLINED)

class PrevEditLineCommand(sublime_plugin.TextCommand):
def run(self, edit):
window = sublime.active_window()
view = window.active_view() if window != None else None
if view is None or view.id() != 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):
window = sublime.active_window()
view = window.active_view() if window != None else None
if view is None or view.id() != 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 QuickEditsCommand(sublime_plugin.TextCommand):
def run(self, edit):
window = sublime.active_window()
view = window.active_view() if window != None else None
if view is None or view.id() != self.view.id():
sublime.status_message(‘Click into the view/tab first.’)
return
self.vid = self.view.id()
edited = adjustEdits(self.view)
if not edited:
sublime.status_message(‘No edits to list.’)
return
the_edits = ]
for i, r in enumerate(edited):
curr_line, _ = self.view.rowcol(r.begin())
curr_text = self.view.substr®.strip():40]
if not len(curr_text):
curr_text = self.view.substr(self.view.line®).strip():40]
+ " (line)"
the_edits.append(“Line: %03d %s” % ( curr_line + 1, curr_text ))
window.show_quick_panel(the_edits, self.on_chosen)

def on_chosen(self, index):
    if index == -1: return
    window = sublime.active_window()
    view = window.active_view() if window != None else None
    if view is None or view.id() != 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 CaptureEditing(sublime_plugin.EventListener):
def on_modified(self, view):
# create hidden regions that mirror the edited regions
window = sublime.active_window()
curr_view = window.active_view() if window != None else None
if curr_view is None or curr_view.id() != view.id():
return
sel = view.sel()[0]
currA, currB = (sel.begin(), sel.end())
self.curr_line, _ = view.rowcol(currA)
if not hasattr(self, ‘prev_line’):
self.prev_line = self.curr_line
if currA > 0 and sel.empty():
currA -= 1
self.lastx, self.lasty = (currA, currB)
self.curr_edit = sublime.Region(self.lastx, self.lasty)
view.add_regions(“edited_rgn”,[self.curr_edit],
“keyword”, sublime.HIDDEN | sublime.PERSISTENT)
return
if self.curr_line == self.prev_line:
self.lastx = min(currA, self.lastx)
self.lasty = max(currB, self.lasty)
self.curr_edit = sublime.Region(self.lastx, self.lasty)
view.add_regions(“edited_rgn”,[self.curr_edit],
“keyword”, sublime.HIDDEN | sublime.PERSISTENT)
else:
self.prev_line = self.curr_line
if currA > 0 and sel.empty():
currA -= 1
self.lastx, self.lasty = (currA, currB)
_ = adjustEdits(view)[/code]

0 Likes

#26

My GitHub includes an icon :smile:

0 Likes

#27

Different icon - it’s a pencil :smiley: .

I shall stop now! But, of course, feedback is encouraged. Andy.


0 Likes

#28

@SimFox3
I’ve replied to your email but my responses are just sitting in my Outbox?!

You enquired about my **LastEditLine **command and I did copy this file initially to work on - but I got side-tracked and this revision doesn’t have the same feature-set. That is, it’s completely different :smiley: .

LastEditLine cycles through the last edit positions similar to the behaviour of Word (when pressing Shift F5). This was my intention, but I might revisit it once I’ve completed my AndyEdits :wink:

LastEditLine.zip (1.12 KB)*

0 Likes

#29

Someone has raised the following two issues:

[quote]
Hi, nice plugin, thanks for sharing.

I found 2 issues with it:

  1. When you change line, you need to make 2 change to trigger the edit icon on this line.
  2. When you trigger undo, the wrong edit icon is removed. To reproduce, modify 3 consecutive lines to show the edit icon on each, trigger undo -> the second to last icon is removed, not the last one. The subsequent undo work fine.

Number 2 is probably a ST2 bug, I’ve found regions in ST2 sometimes unreliable, especially with undo.[/quote]

I shall investigate these. If someone has experience with the Undo actions I will appreciate advice. I suspect it cannot be worked around; at least, not without substantial coding?

Actually, this sounds like the same issue, so if I fix the first one…

0 Likes

#30

I’ve fixed the first issue at my GitHub. I won’t post the code here, as it is now part of a package (including the icon) unless someone requests me to. This will also make the Undo behave slightly better, but I think the previously mentioned issue persists to some extent. I doubt that this can be fully overcome.

There is now an option to remove the edit history for a region (via a quick panel). This could be useful if you are performing a lot of edits on the same file and don’t wish to lose all the edit history by closing the file.

This new remove option enables you to use this package as a kind of workflow. Start different areas with a comment, so that this comment will appear as a title in the quick-panel list of edits. The continuous lines following the comment will be treated as a single edit-item. When completed, using Ctrl+Alt+D (or other shortcut) to *complete *the task. Added: Hint - you can also cut and paste the same line and it will be added as a new edit-item :sunglasses:


0 Likes

#31

I can confirm it’s fixed.

For the undo issue, I suppose only jps can give an answer…

0 Likes

#32

Undo seems to behave a little better now, as a result of the other fix. I think cutting and pasting the same content will be one way to correct edited regions, or even over-writing an area with a space or character, and then using Undo.

It just occurred to me that it *might *be useful to have another shortcut to specifically add the currently highlighted text as an edit-region. I might play with this. However, cutting and pasting achieves the same (or similar) result.

0 Likes

#33

Didn’t change anything for my test case but maybe it’s better for other test case.
Strangely, soft undo works without any issue for me.

So it looks more and more like a ST2 bug.
As jps not very active in this forum since a few weeks, anyone has it’s phone number :wink:

0 Likes

#34

I believe Undo (and other features - Duplicate, Cut, Paste) can be problematic with any plug-in. But for this simple plug-in it shouldn’t be a major concern, and most of these features seem to behave well. I find if I highlight an area, press Space and then Undo (Ctrl-Z) the whole area becomes a single edit-region. This process also removes any smaller edit regions within this larger area. In view of this, I don’t think it’s necessary for me to create a shortcut to do this (as mentioned in my previous post).

If anyone can think of something useful that might be added to this, please let me know - I might consider it :wink: . Otherwise, I think it’s complete - and I’m very pleased with it :smiley: :sunglasses:

0 Likes

#35

IMHO, it’s a major issue. I had the same issue with another plugin (Edit History).
If every time I trigger an undo (which is fairly often for me :frowning: ) it remove a line that don’t have to be removed, and keep a line that have to be removed, it quickly become a mess.

I really like this plugin, but without this issue resolved I don’t think I will use it since I’ve no confidence in it.

0 Likes

#36

I shall investigate! Is it just Undo that causes you concern/issues?

0 Likes

#37

Trying a few things with this Undo issue, including doing nothing :laughing: :

def doNothing(view): view.add_regions("doh", [sublime.Region(0, 0)], "nothing", sublime.HIDDEN)

It’s kind-of a catch-22: writing code to correct Undo, the code we write becomes the *thing *that is undone. I recall others discussing this before; hopefully someone might offer some advice based on their experience :smile:.

I’ve edited my GitHub so that the region for the currently edited line doesn’t extend beyond the end of the line. So, for example, pressing Backspace doesn’t cause the region to extend to the next line. This means that we can use, for example, Ctrl-Backspace to delete the previous word, in preference to Ctrl-Z.

0 Likes

#38

[quote=“agibsonsw”]

I shall investigate! Is it just Undo that causes you concern/issues?[/quote]

Actually yes.
I don’t have any other issue with your plugin, and this undo issue is not an issue from your plugin.

Do you mind if I try to open a new [BUG] topic about this subject ? Maybe jps will give us more info about this issue.

0 Likes

#39

That’s fine. I shall still pursue this Undo issue myself: if I can trace the “sequence of events” then there might be a way around it.

I was thinking if I store variables x, y that represent the *previous *edit position (perhaps as globals), then I can check for “undo” being pressed once so that “on the next update” - re-instate the region x,y. The difference here is that x,y would be integers, rather than reflecting part of the buffer; and this change would occur on the next update, rather than happening straight-away (and being undone!). But it would help if I fully understood the sequence of events.

Andy.

0 Likes