~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to hunk_selector.py

  • Committer: Aaron Bentley
  • Date: 2006-03-07 15:23:15 UTC
  • mfrom: (321.1.2 bzrtools)
  • mto: (147.4.31 trunk)
  • mto: This revision was merged to the branch mainline in revision 324.
  • Revision ID: abentley@panoramicfeedback.com-20060307152315-42337454a0ad956b
MergeĀ fromĀ mainline

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
 
1
3
import sys
2
4
 
3
 
from userinteractor import UserInteractor, UserOption
4
 
from errors import NoColor
5
 
import copy
6
 
 
7
5
class HunkSelector:
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
 
 
 
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):
53
33
        self.patches = patches
54
34
        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
 
 
61
35
        for patch in patches:
62
36
            for hunk in patch.hunks:
63
 
                # everything's selected by default
 
37
                # everything's shelved by default
64
38
                hunk.selected = True
65
39
                self.total_hunks += 1
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):
 
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):
110
133
        for patch in self.patches:
111
134
            for hunk in patch.hunks:
112
135
                if hunk.__dict__.has_key('selected'):
113
136
                    hunk.selected = not hunk.selected
114
137
                else:
115
138
                    hunk.selected = True
116
 
        self._status()
117
 
        return False
118
139
 
119
 
    # The user wants to see the status
120
 
    def _status(self, hunk=None):
 
140
    def __show_status(self):
121
141
        print '\nStatus:'
122
142
        for patch in self.patches:
123
143
            print '  %s' % patch.oldname
124
 
            selected = 0
125
 
            unselected = 0
 
144
            shelve = 0
 
145
            keep = 0
126
146
            for hunk in patch.hunks:
127
147
                if hunk.selected:
128
 
                    selected += 1
 
148
                    shelve += 1
129
149
                else:
130
 
                    unselected += 1
 
150
                    keep += 1
131
151
 
132
 
            print '  ', self.strings['status_selected'] % selected
133
 
            print '  ', self.strings['status_unselected'] % unselected
 
152
            print '    %d hunks to be shelved' % shelve
 
153
            print '    %d hunks to be kept' % keep
134
154
            print
135
155
 
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)
 
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)