Home Download Buy Blog Forum Support

Split/iterate [regions]

Re: Split/iterate [regions]

Postby agibsonsw on Sun Oct 07, 2012 5:44 pm

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).

Code: Select all
    { "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: Select all
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(r)
        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(r).strip()[:40]
            if not len(curr_text):
                curr_text = self.view.substr(self.view.line(r)).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)

Not bad for a few hours work - I might actually use this :lol:
Attachments
AndyEdits.zip
(1.28 KiB) Downloaded 77 times
"I'm here to save your life. But if I'm going to do that, I'll need total uninanonynymity." Me Myself & Irene.
agibsonsw
 
Posts: 901
Joined: Fri Jan 27, 2012 9:11 pm

Re: Split/iterate [regions]

Postby agibsonsw on Sun Oct 07, 2012 6:08 pm

Talking of JavaScript prototypes..

Code: Select all
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 8-)
[There are other versions than mine, but mine distinguishes between AM and am and enables suffixes 1st ,2nd etc..]
"I'm here to save your life. But if I'm going to do that, I'll need total uninanonynymity." Me Myself & Irene.
agibsonsw
 
Posts: 901
Joined: Fri Jan 27, 2012 9:11 pm

Re: Split/iterate [regions]

Postby agibsonsw on Sun Oct 07, 2012 6:54 pm

Attached screenshot.
edits1.png
edits1.png (27.68 KiB) Viewed 1378 times
"I'm here to save your life. But if I'm going to do that, I'll need total uninanonynymity." Me Myself & Irene.
agibsonsw
 
Posts: 901
Joined: Fri Jan 27, 2012 9:11 pm

Re: Split/iterate [regions]

Postby agibsonsw on Sun Oct 07, 2012 7:47 pm

Second/last screenshot.

edits2.png
edits2.png (16.46 KiB) Viewed 1370 times
"I'm here to save your life. But if I'm going to do that, I'll need total uninanonynymity." Me Myself & Irene.
agibsonsw
 
Posts: 901
Joined: Fri Jan 27, 2012 9:11 pm

Re: Split/iterate [regions]

Postby agibsonsw on Sun Oct 07, 2012 9:19 pm

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: Select all
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(r)
        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(r).strip()[:40]
            if not len(curr_text):
                curr_text = self.view.substr(self.view.line(r)).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)
"I'm here to save your life. But if I'm going to do that, I'll need total uninanonynymity." Me Myself & Irene.
agibsonsw
 
Posts: 901
Joined: Fri Jan 27, 2012 9:11 pm

Re: Split/iterate [regions]

Postby agibsonsw on Sun Oct 07, 2012 10:09 pm

My GitHub includes an icon :)
"I'm here to save your life. But if I'm going to do that, I'll need total uninanonynymity." Me Myself & Irene.
agibsonsw
 
Posts: 901
Joined: Fri Jan 27, 2012 9:11 pm

Re: Split/iterate [regions]

Postby agibsonsw on Sun Oct 07, 2012 11:33 pm

Different icon - it's a pencil :D .

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

edits3.png
edits3.png (46.49 KiB) Viewed 1376 times
"I'm here to save your life. But if I'm going to do that, I'll need total uninanonynymity." Me Myself & Irene.
agibsonsw
 
Posts: 901
Joined: Fri Jan 27, 2012 9:11 pm

Re: Split/iterate [regions]

Postby agibsonsw on Mon Oct 08, 2012 12:30 pm

@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 :D .

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 ;)

[I did start by copying LastEditLine, but then I deleted 90% of the code, he, he.]
Attachments
LastEditLine.zip
(1.12 KiB) Downloaded 75 times
"I'm here to save your life. But if I'm going to do that, I'll need total uninanonynymity." Me Myself & Irene.
agibsonsw
 
Posts: 901
Joined: Fri Jan 27, 2012 9:11 pm

Re: Split/iterate [regions]

Postby agibsonsw on Mon Oct 08, 2012 12:47 pm

Someone has raised the following two issues:
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.

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..
"I'm here to save your life. But if I'm going to do that, I'll need total uninanonynymity." Me Myself & Irene.
agibsonsw
 
Posts: 901
Joined: Fri Jan 27, 2012 9:11 pm

Re: Split/iterate [regions]

Postby agibsonsw on Mon Oct 08, 2012 1:46 pm

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 8-)

edits4.png
edits4.png (19.89 KiB) Viewed 1308 times
Last edited by agibsonsw on Mon Oct 08, 2012 2:19 pm, edited 1 time in total.
"I'm here to save your life. But if I'm going to do that, I'll need total uninanonynymity." Me Myself & Irene.
agibsonsw
 
Posts: 901
Joined: Fri Jan 27, 2012 9:11 pm

PreviousNext

Return to Technical Support

Who is online

Users browsing this forum: No registered users and 30 guests