_check_scrolled()   F
last analyzed

Complexity

Conditions 11

Size

Total Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
dl 0
loc 37
rs 3.1764
c 0
b 0
f 0

How to fix   Complexity   

Complexity

Complex classes like _check_scrolled() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
from itertools import count
2
import time
3
4
from pytknvim.tk_ui import KEY_TABLE, _stringify_key
5
6
7
MAX_SCROLL = 10
8
BUFFER_NUM = 1
9
# TODO GET THIS DYNAMICALLY
10
STATUS_BAR_HEIGHT = 3
11
12
13
class Unnest(Exception):
14
    '''Used to exit a nested loop'''
15
    pass
16
17
18
def _textwidget_rows(widget):
19
    '''Return all tkinter chars as rows'''
20
    # Rows start counting at 1 in tkinter text widget
21
    end_row, end_col = (int(i) for i in
22
                        widget.index('end-1c').split('.'))
23
    try:
24
        for row in count(1):
25
            line = []
26
            for col in count(0):
27
                # Exit out
28
                if end_row == row:
29
                   if end_col == col:
30
                       raise Unnest
31
                # Add if not on new line
32
                char = widget.get('{0}.{1}'.format(row,col))
33
                line.append(char)
34
                if char == '\n':
35
                    yield ''.join(i for i in line)
36
                    break
37
    except Unnest:
38
        pass
39
40
41
def _nvim_rows(buff):
42
    '''get all neovim rows'''
43
    all_rows = []
44
    for row in buff:
45
        all_rows.append(row)
46
    return all_rows
47
48
49
def _screen_rows(cells):
50
    '''get all rows of the internal screen '''
51
    for row in cells:
52
        line = []
53
        for char in row:
54
            line.append(char.text)
55
        yield ''.join(i for i in line)
56
57
58
def _remove_sidebar(line):
59
    '''remove the numbers and space if they are there'''
60
    found_numbers = False
61
    found_char = False
62
    for i, char in enumerate(line):
63
        if not found_numbers:
64
            try:
65
                int(char)
66
            except ValueError:
67
                if char != ' ':
68
                    found_char = True
69
                continue
70
            else:
71
                found_numbers = True
72
        else:
73
            try:
74
                int(char)
75
            except ValueError:
76
                break
77
            else:
78
                continue
79
    if found_numbers:
80
        if not found_char:
81
            return line[i+1:]
82
        else:
83
            raise Exception('chars where found in sidebar...',
84
                            line)
85
    else:
86
        return line
87
88
89
def _parse(lines, line_length, mock_inst, eol_trim):
90
    '''
91
    make the values for screen and tkinter text widget
92
    look like neovims values,
93
    neovim doesn't give us the ~ at the start,
94
    also remove our newline chars and end spacing
95
    also remove status bar stuff
96
    '''
97
    # Unfortunatley the code is a bit confusing
98
    # I thought the handling was more similar than
99
    # different for the two cases...
100
    file_name =  mock_inst.test_nvim.eval('expand("%:t")')
101
    side_bar = mock_inst.test_nvim.eval('&relativenumber') \
102
                        or mock_inst.test_nvim.eval('&number')
103
    all_rows = []
104
    for i, line in enumerate(lines):
105
        # screen doesn't have a \n
106
        if eol_trim:
107
            assert line[-eol_trim:] == '\n'
108
        try:
109
            assert len(line)-eol_trim  == line_length
110
        except AssertionError:
111
            # TODO does this line length need to match?
112
            if '-- INSERT --' not in line:
113
                raise
114
            break
115
        if side_bar:
116
            line = _remove_sidebar(line)
117
        if line[0] == '~':
118
            if eol_trim:
119
                parsed = line[1:-eol_trim].rstrip()
120
            else:
121
                parsed = line[1:].rstrip()
122
            if not parsed:
123
                # do not add blank lists
124
                continue
125
        else:
126
            if eol_trim:
127
                parsed = line[:-eol_trim].rstrip()
128
            else:
129
                parsed = line.rstrip()
130
            if not parsed:
131
                parsed = ''
132
        all_rows.append(parsed)
133
134
    # Remove the status bar (screen has a new line padded after)
135
    for i in range(1, 3):
136
        if '[No Name]' in all_rows[-i]:
137
            del all_rows[-i:]
138
            break
139
        elif file_name and file_name in all_rows[-i]:
140
            del all_rows[-i-1:]
141
            break
142
    else:
143
        raise Exception("couldn't find status bar...")
144
    return all_rows
145
146
147
def _parse_text(lines, line_length, mock_inst):
148
    return _parse(lines, line_length, mock_inst, eol_trim=1)
149
150
151
def _parse_screen(lines, line_length, mock_inst):
152
    return _parse(lines, line_length, mock_inst ,eol_trim=0)
153
154
155
def _check_scrolled(screen, nvim_rows, parsed_thing,
156
                    max_scroll):
157
    '''
158
    the screen and text widget only ever store
159
    what is being showed to the user,
160
    while neovim gives the entire buffer.
161
162
    we try matching slices of the lists to check
163
    if it has been scrolled
164
    checks max 10 scrolls forward and 10 backward
165
    '''
166
    viewable = len(parsed_thing)
167
    screen_size = screen.bot - screen.top
168
    if viewable != screen_size:
169
        maybe_status_bar = screen_size - viewable
170
        if maybe_status_bar > STATUS_BAR_HEIGHT:
171
            raise Exception('fix this...')
172
    for i in range(0, max_scroll + 1):
173
        end_index = i + viewable
174
        try:
175
            # CHECK IF SCROLLED DOWN
176
            assert nvim_rows[i:end_index] == parsed_thing
177
        except AssertionError:
178
            pass
179
        else:
180
            return True
181
        try:
182
            # CHECK IF SCROLLED UP
183
            assert nvim_rows[-end_index:-i] == parsed_thing
184
        except AssertionError:
185
            pass
186
        else:
187
            return True
188
    else:
189
        raise AssertionError('nvim rows did not match..\n \
190
                    make sure scroll amount is less than '
191
                    + str(max_scroll))
192
193
194
def compare_screens(mock_inst):
195
    '''
196
    compares our text widget values with the nvim values.
197
    compares our internal screen with text widget
198
199
    nvim only makes the text (no spacing or newlines avaliable)
200
201
    '''
202
    line_length = mock_inst._screen.columns
203
204
    nvim_rows = _nvim_rows(mock_inst.test_nvim.buffers[BUFFER_NUM])
205
    text_rows = _textwidget_rows(mock_inst.text)
206
    screen_rows = _screen_rows(mock_inst._screen._cells)
207
208
    parsed_text = _parse_text(text_rows, line_length, mock_inst)
209
    parsed_screen = _parse_screen(screen_rows, line_length,
210
                                  mock_inst)
211
    try:
212
        assert len(parsed_screen) == len(parsed_text)
213
    except:
214
        import pdb;pdb.set_trace()
215
    try:
216
        assert len(nvim_rows) == len(parsed_screen)
217
    # A scroll may have happend
218
    except AssertionError:
219
        _check_scrolled(mock_inst._screen,
220
                       nvim_rows,
221
                       parsed_text,
222
                       mock_inst.max_scroll)
223
        # TODO move this inside _check_scrolled
224
        # can use the offset to check all cells
225
        for sr, tr in zip(parsed_screen, parsed_text):
226
            assert sr == tr
227
    else:
228
        # No Scroll happened they should all match
229
        for sr, tr, nr in zip(parsed_screen,
230
                              parsed_text, nvim_rows):
231
            assert sr == tr == nr
232
233
234
class Event():
235
    def __init__(self, key, modifyer=None):
236
        '''
237
        mimics a tkinter key press event.
238
        this just fudges it enough so it passes the checks
239
        for our function...
240
        '''
241
        self.keysym = key
242
        self.char = key
243
        self.state = 0
244
        self.keycode = ord(key)
245
        self.keysym_num= ord(key)
246
        if modifyer:
247
            self.state = 1337
248
            self.keysym = modifyer.capitalize()
249
250
251
def send_tk_key(tknvim, key, modifyer=None):
252
    '''
253
    send a key through to our class as a tkinter event
254
    passed as tkinter or vim keys i.e Esc
255
    pass in a modifyer as, shift, alt, or ctrl
256
    '''
257
    assert modifyer in ('shift', 'alt', 'ctrl', None)
258
    if len(key) == 1:
259
        event = Event(key, modifyer)
260
        tknvim.nvim_handler.tk_key_pressed(event)
261
    else:
262
        # Special key
263
        for value in KEY_TABLE.values():
264
            if value == key:
265
                break
266
        else:
267
            if key in KEY_TABLE:
268
                key = KEY_TABLE[key]
269
            else:
270
                raise KeyError(
271
                        'Please pass an acceptable key in')
272
        vimified = _stringify_key(key, [])
273
        tknvim.nvim_handler._bridge.input(vimified)
274
    time.sleep(0.02)
275