~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to hunk_selector.py

  • Committer: Aaron Bentley
  • Date: 2013-08-20 03:02:43 UTC
  • Revision ID: aaron@aaronbentley.com-20130820030243-r8v1xfbcnd8f10p4
Fix zap command for 2.6/7

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python
2
 
 
3
1
import sys
4
2
 
 
3
from userinteractor import UserInteractor, UserOption
 
4
from errors import NoColor, NoBzrtoolsColor
 
5
import copy
 
6
 
5
7
class HunkSelector:
6
 
    class Option:
7
 
        def __init__(self, char, action, help, default=False):
8
 
            self.char = char
9
 
            self.action = action
10
 
            self.default = default
11
 
            self.help = help
12
 
 
13
 
    standard_options = [
14
 
        Option('n', 'shelve', 'shelve this change for the moment.',
15
 
            default=True),
16
 
        Option('y', 'keep', 'keep this change in your tree.'),
17
 
        Option('d', 'done', 'done, skip to the end.'),
18
 
        Option('i', 'invert', 'invert the current selection of all hunks.'),
19
 
        Option('s', 'status', 'show status of hunks.'),
20
 
        Option('q', 'quit', 'quit')
21
 
    ]
22
 
 
23
 
    end_options = [
24
 
        Option('y', 'continue', 'proceed to shelve selected changes.',
25
 
            default=True),
26
 
        Option('r', 'restart', 'restart the hunk selection loop.'),
27
 
        Option('s', 'status', 'show status of hunks.'),
28
 
        Option('i', 'invert', 'invert the current selection of all hunks.'),
29
 
        Option('q', 'quit', 'quit')
30
 
    ]
31
 
 
32
 
    def __init__(self, patches):
 
8
    strings = {}
 
9
 
 
10
    def __init__(self, patches, color=None):
 
11
        if color is True or color is None:
 
12
            try:
 
13
                from colordiff import DiffWriter
 
14
                from terminal import has_ansi_colors
 
15
                if has_ansi_colors():
 
16
                    self.diff_stream = DiffWriter(sys.stdout,
 
17
                                                  check_style=False)
 
18
                else:
 
19
                    if color is True:
 
20
                        raise NoColor()
 
21
                    self.diff_stream = sys.stdout
 
22
            except ImportError:
 
23
                if color is True:
 
24
                    raise NoBzrtoolsColor()
 
25
                self.diff_stream = sys.stdout
 
26
        else:
 
27
            self.diff_stream = sys.stdout
 
28
 
 
29
        self.standard_options = [
 
30
            UserOption('y', self._selected, self.strings['select_desc'],
 
31
                default=True),
 
32
            UserOption('n', self._unselected, self.strings['unselect_desc']),
 
33
            UserOption('d', UserInteractor.FINISH, 'done, skip to the end.'),
 
34
            UserOption('i', self._invert,
 
35
                'invert the current selection status of all hunks.'),
 
36
            UserOption('s', self._status,
 
37
                'show selection status of all hunks.'),
 
38
            UserOption('q', UserInteractor.QUIT, 'quit')
 
39
        ]
 
40
 
 
41
        self.end_options = [
 
42
            UserOption('y', UserInteractor.FINISH, self.strings['finish_desc'],
 
43
                default=True),
 
44
            UserOption('r', UserInteractor.RESTART,
 
45
                'restart the hunk selection loop.'),
 
46
            UserOption('s', self._status,
 
47
                'show selection status of all hunks.'),
 
48
            UserOption('i', self._invert,
 
49
                'invert the current selection status of all hunks.'),
 
50
            UserOption('q', UserInteractor.QUIT, 'quit')
 
51
        ]
 
52
 
33
53
        self.patches = patches
34
54
        self.total_hunks = 0
 
55
 
 
56
        self.interactor = UserInteractor()
 
57
        self.interactor.set_item_callback(self._hunk_callback)
 
58
        self.interactor.set_start_callback(self._start_callback)
 
59
        self.interactor.set_end_callback(self._end_callback)
 
60
 
35
61
        for patch in patches:
36
62
            for hunk in patch.hunks:
37
 
                # everything's shelved by default
 
63
                # everything's selected by default
38
64
                hunk.selected = True
39
65
                self.total_hunks += 1
40
 
 
41
 
    def __get_option(self, char):
42
 
        for opt in self.standard_options:
43
 
            if opt.char == char:
44
 
                return opt
45
 
        raise Exception('Option "%s" not found!' % char)
46
 
 
47
 
    def __select_loop(self):
48
 
        j = 0
49
 
        for patch in self.patches:
50
 
            i = 0
51
 
            lasti = -1
52
 
            while i < len(patch.hunks):
53
 
                hunk = patch.hunks[i]
54
 
                if lasti != i:
55
 
                    print patch.get_header(), hunk
56
 
                    j += 1
57
 
                lasti = i
58
 
 
59
 
                prompt = 'Keep this change? (%d of %d)' \
60
 
                            % (j, self.total_hunks)
61
 
 
62
 
                if hunk.selected:
63
 
                    self.__get_option('n').default = True
64
 
                    self.__get_option('y').default = False
65
 
                else:
66
 
                    self.__get_option('n').default = False
67
 
                    self.__get_option('y').default = True
68
 
 
69
 
                action = self.__ask_user(prompt, self.standard_options)
70
 
 
71
 
                if action == 'keep':
72
 
                    hunk.selected = False
73
 
                elif action == 'shelve':
74
 
                    hunk.selected = True
75
 
                elif action == 'done':
76
 
                    return True
77
 
                elif action == 'invert':
78
 
                    self.__invert_selection()
79
 
                    self.__show_status()
80
 
                    continue
81
 
                elif action == 'status':
82
 
                    self.__show_status()
83
 
                    continue
84
 
                elif action == 'quit':
85
 
                    return False
86
 
 
87
 
                i += 1
88
 
        return True
89
 
 
90
 
    def select(self):
91
 
        if self.total_hunks == 0:
92
 
            return []
93
 
 
94
 
        done = False
95
 
        while not done:
96
 
            if not self.__select_loop():
97
 
                return []
98
 
 
99
 
            while True:
100
 
                self.__show_status()
101
 
                prompt = "Shelve these changes, or restart?"
102
 
                action = self.__ask_user(prompt, self.end_options)
103
 
 
104
 
                if action == 'continue':
105
 
                    done = True
106
 
                    break
107
 
                elif action == 'quit':
108
 
                    return []
109
 
                elif action == 'status':
110
 
                    self.__show_status()
111
 
                elif action == 'invert':
112
 
                    self.__invert_selection()
113
 
                elif action == 'restart':
114
 
                    break
115
 
 
116
 
 
117
 
        for patch in self.patches:
118
 
            tmp = []
119
 
            for hunk in patch.hunks:
120
 
                if hunk.selected:
121
 
                    tmp.append(hunk)
122
 
            patch.hunks = tmp
123
 
 
124
 
        tmp = []
125
 
        for patch in self.patches:
126
 
            if len(patch.hunks):
127
 
                tmp.append(patch)
128
 
        self.patches = tmp
129
 
 
130
 
        return self.patches
131
 
 
132
 
    def __invert_selection(self):
 
66
                # we need a back pointer in the callbacks
 
67
                hunk.patch = patch
 
68
                self.interactor.add_item(hunk)
 
69
 
 
70
    # Called at the start of the main loop
 
71
    def _start_callback(self):
 
72
        self.last_printed = -1
 
73
        self.interactor.set_prompt(self.strings['prompt'])
 
74
        self.interactor.set_options(self.standard_options)
 
75
 
 
76
    # Called at the end of the item loop, return False to indicate that the
 
77
    # interaction isn't finished and the confirmation prompt should be displayed
 
78
    def _end_callback(self):
 
79
        self._status()
 
80
        self.interactor.set_prompt(self.strings['end_prompt'])
 
81
        self.interactor.set_options(self.end_options)
 
82
        return False
 
83
 
 
84
    # Called once for each hunk
 
85
    def _hunk_callback(self, hunk, count):
 
86
        if self.last_printed != count:
 
87
            self.diff_stream.write(str(hunk.patch.get_header()))
 
88
            self.diff_stream.write(str(hunk))
 
89
            self.last_printed = count
 
90
 
 
91
        if hunk.selected:
 
92
            self.interactor.get_option('y').default = True
 
93
            self.interactor.get_option('n').default = False
 
94
        else:
 
95
            self.interactor.get_option('y').default = False
 
96
            self.interactor.get_option('n').default = True
 
97
 
 
98
    # The user chooses to (un)shelve a hunk
 
99
    def _selected(self, hunk):
 
100
        hunk.selected = True
 
101
        return True
 
102
 
 
103
    # The user chooses to keep a hunk
 
104
    def _unselected(self, hunk):
 
105
        hunk.selected = False
 
106
        return True
 
107
 
 
108
    # The user chooses to invert the selection
 
109
    def _invert(self, hunk):
133
110
        for patch in self.patches:
134
111
            for hunk in patch.hunks:
135
112
                if hunk.__dict__.has_key('selected'):
136
113
                    hunk.selected = not hunk.selected
137
114
                else:
138
115
                    hunk.selected = True
 
116
        self._status()
 
117
        return False
139
118
 
140
 
    def __show_status(self):
 
119
    # The user wants to see the status
 
120
    def _status(self, hunk=None):
141
121
        print '\nStatus:'
142
122
        for patch in self.patches:
143
123
            print '  %s' % patch.oldname
144
 
            shelve = 0
145
 
            keep = 0
 
124
            selected = 0
 
125
            unselected = 0
146
126
            for hunk in patch.hunks:
147
127
                if hunk.selected:
148
 
                    shelve += 1
 
128
                    selected += 1
149
129
                else:
150
 
                    keep += 1
 
130
                    unselected += 1
151
131
 
152
 
            print '    %d hunks to be shelved' % shelve
153
 
            print '    %d hunks to be kept' % keep
 
132
            print '  ', self.strings['status_selected'] % selected
 
133
            print '  ', self.strings['status_unselected'] % unselected
154
134
            print
155
135
 
156
 
    if sys.platform == "win32":
157
 
        import msvcrt
158
 
        def __getchar(self):
159
 
            return msvcrt.getche()
160
 
    else:
161
 
        def __getchar(self):
162
 
            import tty
163
 
            import termios
164
 
            fd = sys.stdin.fileno()
165
 
            settings = termios.tcgetattr(fd)
166
 
            try:
167
 
                tty.setraw(fd)
168
 
                ch = sys.stdin.read(1)
169
 
            finally:
170
 
                termios.tcsetattr(fd, termios.TCSADRAIN, settings)
171
 
            return ch
172
 
 
173
 
    def __ask_user(self, prompt, options):
174
 
        while True:
175
 
            sys.stdout.write(prompt)
176
 
            sys.stdout.write(' [')
177
 
            for opt in options:
178
 
                if opt.default:
179
 
                    default = opt
180
 
                sys.stdout.write(opt.char)
181
 
            sys.stdout.write('?] (%s): ' % default.char)
182
 
 
183
 
            response = self.__getchar()
184
 
 
185
 
            # default, which we see as newline, is 'n'
186
 
            if response in ['\n', '\r', '\r\n']:
187
 
                response = default.char
188
 
 
189
 
            print response # because echo is off
190
 
 
191
 
            for opt in options:
192
 
                if opt.char == response:
193
 
                    return opt.action
194
 
 
195
 
            for opt in options:
196
 
                print '  %s - %s' % (opt.char, opt.help)
 
136
        # Tell the interactor we're not done with this item
 
137
        return False
 
138
 
 
139
    def select(self):
 
140
        if self.total_hunks == 0 or not self.interactor.interact():
 
141
            # False from interact means they chose to quit
 
142
            return ([], [])
 
143
 
 
144
        # Go through each patch and collect all selected/unselected hunks
 
145
        for patch in self.patches:
 
146
            patch.selected = []
 
147
            patch.unselected = []
 
148
            for hunk in patch.hunks:
 
149
                if hunk.selected:
 
150
                    patch.selected.append(hunk)
 
151
                else:
 
152
                    patch.unselected.append(hunk)
 
153
 
 
154
        # Now build two lists, one of selected patches the other unselected
 
155
        selected_patches = []
 
156
        unselected_patches = []
 
157
 
 
158
        for patch in self.patches:
 
159
            if len(patch.selected):
 
160
                tmp = copy.copy(patch)
 
161
                tmp.hunks = tmp.selected
 
162
                del tmp.selected
 
163
                del tmp.unselected
 
164
                selected_patches.append(tmp)
 
165
 
 
166
            if len(patch.unselected):
 
167
                tmp = copy.copy(patch)
 
168
                tmp.hunks = tmp.unselected
 
169
                del tmp.selected
 
170
                del tmp.unselected
 
171
                unselected_patches.append(tmp)
 
172
 
 
173
        return (selected_patches, unselected_patches)
 
174
 
 
175
class ShelveHunkSelector(HunkSelector):
 
176
    def __init__(self, patches, color=None):
 
177
        self.strings = {}
 
178
        self.strings['status_selected'] = '%d hunks to be shelved'
 
179
        self.strings['status_unselected'] = '%d hunks to be kept'
 
180
        self.strings['select_desc'] = 'shelve this change.'
 
181
        self.strings['unselect_desc'] = 'keep this change in your tree.'
 
182
        self.strings['finish_desc'] = 'shelve selected changes.'
 
183
        self.strings['prompt'] = 'Shelve this change? (%(count)d of %(total)d)'
 
184
        self.strings['end_prompt'] = 'Shelve these changes?'
 
185
        HunkSelector.__init__(self, patches, color)
 
186
 
 
187
class UnshelveHunkSelector(HunkSelector):
 
188
    def __init__(self, patches, color=None):
 
189
        self.strings = {}
 
190
        self.strings['status_selected'] = '%d hunks to be unshelved'
 
191
        self.strings['status_unselected'] = '%d hunks left on shelf'
 
192
        self.strings['select_desc'] = 'unshelve this change.'
 
193
        self.strings['unselect_desc'] = 'leave this change on the shelf.'
 
194
        self.strings['finish_desc'] = 'unshelve selected changes.'
 
195
        self.strings['prompt'] = 'Unshelve this change? ' \
 
196
            '(%(count)d of %(total)d)'
 
197
        self.strings['end_prompt'] = 'Unshelve these changes?'
 
198
        HunkSelector.__init__(self, patches, color)