| Total Complexity | 111 |
| Total Lines | 508 |
| Duplicated Lines | 5.51 % |
| Changes | 8 | ||
| Bugs | 3 | Features | 0 |
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like GtkUI 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 | """Neovim Gtk+ UI.""" |
||
| 72 | class GtkUI(object): |
||
| 73 | |||
| 74 | """Gtk+ UI class.""" |
||
| 75 | |||
| 76 | def __init__(self, config): |
||
| 77 | """Initialize the UI instance.""" |
||
| 78 | self._redraw_arg = None |
||
| 79 | self._foreground = config.get('foreground', -1) |
||
| 80 | self._background = config.get('background', -1) |
||
| 81 | self._window_background = config.get('window_background', '#aaaaaa') |
||
| 82 | self._font_size = config.get('font_size', 13) |
||
| 83 | self._font_name = config.get('font_name', 'Monospace') |
||
| 84 | self._screen = None |
||
| 85 | self._attrs = None |
||
| 86 | self._busy = False |
||
| 87 | self._mouse_enabled = False |
||
| 88 | self._insert_cursor = False |
||
| 89 | self._blink = False |
||
| 90 | self._blink_timer_id = None |
||
| 91 | self._resize_timer_id = None |
||
| 92 | self._pressed = None |
||
| 93 | self._invalid = None |
||
| 94 | self._pending = [0, 0, 0] |
||
| 95 | self._reset_cache() |
||
| 96 | |||
| 97 | def start(self, bridge): |
||
| 98 | """Start the UI event loop.""" |
||
| 99 | bridge.attach(80, 24, True) |
||
| 100 | drawing_area = Gtk.DrawingArea() |
||
| 101 | drawing_area.connect('draw', self._gtk_draw) |
||
| 102 | window = Gtk.Window() |
||
| 103 | window.add(drawing_area) |
||
| 104 | window.set_events(window.get_events() | |
||
| 105 | Gdk.EventMask.BUTTON_PRESS_MASK | |
||
| 106 | Gdk.EventMask.BUTTON_RELEASE_MASK | |
||
| 107 | Gdk.EventMask.POINTER_MOTION_MASK | |
||
| 108 | Gdk.EventMask.SCROLL_MASK) |
||
| 109 | window.connect('configure-event', self._gtk_configure) |
||
| 110 | window.connect('delete-event', self._gtk_quit) |
||
| 111 | window.connect('key-press-event', self._gtk_key) |
||
| 112 | window.connect('key-release-event', self._gtk_key_release) |
||
| 113 | window.connect('button-press-event', self._gtk_button_press) |
||
| 114 | window.connect('button-release-event', self._gtk_button_release) |
||
| 115 | window.connect('motion-notify-event', self._gtk_motion_notify) |
||
| 116 | window.connect('scroll-event', self._gtk_scroll) |
||
| 117 | window.connect('focus-in-event', self._gtk_focus_in) |
||
| 118 | window.connect('focus-out-event', self._gtk_focus_out) |
||
| 119 | window.modify_bg(Gtk.StateType.NORMAL, |
||
| 120 | Gdk.color_parse(self._window_background)) |
||
| 121 | window.show_all() |
||
| 122 | im_context = Gtk.IMMulticontext() |
||
| 123 | im_context.set_client_window(drawing_area.get_window()) |
||
| 124 | im_context.set_use_preedit(False) # TODO: preedit at cursor position |
||
| 125 | im_context.connect('commit', self._gtk_input) |
||
| 126 | self._pango_context = drawing_area.create_pango_context() |
||
| 127 | self._drawing_area = drawing_area |
||
| 128 | self._window = window |
||
| 129 | self._im_context = im_context |
||
| 130 | self._bridge = bridge |
||
| 131 | Gtk.main() |
||
| 132 | |||
| 133 | def quit(self): |
||
| 134 | """Exit the UI event loop.""" |
||
| 135 | GObject.idle_add(Gtk.main_quit) |
||
| 136 | |||
| 137 | def schedule_screen_update(self, apply_updates): |
||
| 138 | """Schedule screen updates to run in the UI event loop.""" |
||
| 139 | def wrapper(): |
||
| 140 | apply_updates() |
||
| 141 | self._flush() |
||
| 142 | self._start_blinking() |
||
| 143 | self._screen_invalid() |
||
| 144 | GObject.idle_add(wrapper) |
||
| 145 | |||
| 146 | def _screen_invalid(self): |
||
| 147 | self._drawing_area.queue_draw() |
||
| 148 | |||
| 149 | def _nvim_resize(self, columns, rows): |
||
| 150 | da = self._drawing_area |
||
| 151 | # create FontDescription object for the selected font/size |
||
| 152 | font_str = '{0} {1}'.format(self._font_name, self._font_size) |
||
| 153 | self._font, pixels, normal_width, bold_width = _parse_font(font_str) |
||
| 154 | # calculate the letter_spacing required to make bold have the same |
||
| 155 | # width as normal |
||
| 156 | self._bold_spacing = normal_width - bold_width |
||
| 157 | cell_pixel_width, cell_pixel_height = pixels |
||
| 158 | # calculate the total pixel width/height of the drawing area |
||
| 159 | pixel_width = cell_pixel_width * columns |
||
| 160 | pixel_height = cell_pixel_height * rows |
||
| 161 | gdkwin = da.get_window() |
||
| 162 | content = cairo.CONTENT_COLOR |
||
| 163 | self._cairo_surface = gdkwin.create_similar_surface(content, |
||
| 164 | pixel_width, |
||
| 165 | pixel_height) |
||
| 166 | self._cairo_context = cairo.Context(self._cairo_surface) |
||
| 167 | self._pango_layout = PangoCairo.create_layout(self._cairo_context) |
||
| 168 | self._pango_layout.set_alignment(Pango.Alignment.LEFT) |
||
| 169 | self._pango_layout.set_font_description(self._font) |
||
| 170 | self._pixel_width, self._pixel_height = pixel_width, pixel_height |
||
| 171 | self._cell_pixel_width = cell_pixel_width |
||
| 172 | self._cell_pixel_height = cell_pixel_height |
||
| 173 | self._screen = Screen(columns, rows) |
||
| 174 | self._window.resize(pixel_width, pixel_height) |
||
| 175 | |||
| 176 | def _nvim_clear(self): |
||
| 177 | self._clear_region(self._screen.top, self._screen.bot + 1, |
||
| 178 | self._screen.left, self._screen.right + 1) |
||
| 179 | self._screen.clear() |
||
| 180 | |||
| 181 | def _nvim_eol_clear(self): |
||
| 182 | row, col = self._screen.row, self._screen.col |
||
| 183 | self._clear_region(row, row + 1, col, self._screen.right + 1) |
||
| 184 | self._screen.eol_clear() |
||
| 185 | |||
| 186 | def _nvim_cursor_goto(self, row, col): |
||
| 187 | self._screen.cursor_goto(row, col) |
||
| 188 | |||
| 189 | def _nvim_busy_start(self): |
||
| 190 | self._busy = True |
||
| 191 | |||
| 192 | def _nvim_busy_stop(self): |
||
| 193 | self._busy = False |
||
| 194 | |||
| 195 | def _nvim_mouse_on(self): |
||
| 196 | self._mouse_enabled = True |
||
| 197 | |||
| 198 | def _nvim_mouse_off(self): |
||
| 199 | self._mouse_enabled = False |
||
| 200 | |||
| 201 | def _nvim_mode_change(self, mode): |
||
| 202 | self._insert_cursor = mode == 'insert' |
||
| 203 | |||
| 204 | def _nvim_set_scroll_region(self, top, bot, left, right): |
||
| 205 | self._screen.set_scroll_region(top, bot, left, right) |
||
| 206 | |||
| 207 | def _nvim_scroll(self, count): |
||
| 208 | self._flush() |
||
| 209 | top, bot = self._screen.top, self._screen.bot + 1 |
||
| 210 | left, right = self._screen.left, self._screen.right + 1 |
||
| 211 | # The diagrams below illustrate what will happen, depending on the |
||
| 212 | # scroll direction. "=" is used to represent the SR(scroll region) |
||
| 213 | # boundaries and "-" the moved rectangles. note that dst and src share |
||
| 214 | # a common region |
||
| 215 | if count > 0: |
||
| 216 | # move an rectangle in the SR up, this can happen while scrolling |
||
| 217 | # down |
||
| 218 | # +-------------------------+ |
||
| 219 | # | (clipped above SR) | ^ |
||
| 220 | # |=========================| dst_top | |
||
| 221 | # | dst (still in SR) | | |
||
| 222 | # +-------------------------+ src_top | |
||
| 223 | # | src (moved up) and dst | | |
||
| 224 | # |-------------------------| dst_bot | |
||
| 225 | # | src (cleared) | | |
||
| 226 | # +=========================+ src_bot |
||
| 227 | src_top, src_bot = top + count, bot |
||
| 228 | dst_top, dst_bot = top, bot - count |
||
| 229 | clr_top, clr_bot = dst_bot, src_bot |
||
| 230 | else: |
||
| 231 | # move a rectangle in the SR down, this can happen while scrolling |
||
| 232 | # up |
||
| 233 | # +=========================+ src_top |
||
| 234 | # | src (cleared) | | |
||
| 235 | # |------------------------ | dst_top | |
||
| 236 | # | src (moved down) and dst| | |
||
| 237 | # +-------------------------+ src_bot | |
||
| 238 | # | dst (still in SR) | | |
||
| 239 | # |=========================| dst_bot | |
||
| 240 | # | (clipped below SR) | v |
||
| 241 | # +-------------------------+ |
||
| 242 | src_top, src_bot = top, bot + count |
||
| 243 | dst_top, dst_bot = top - count, bot |
||
| 244 | clr_top, clr_bot = src_top, dst_top |
||
| 245 | self._cairo_surface.flush() |
||
| 246 | self._cairo_context.save() |
||
| 247 | # The move is performed by setting the source surface to itself, but |
||
| 248 | # with a coordinate transformation. |
||
| 249 | _, y = self._get_coords(dst_top - src_top, 0) |
||
| 250 | self._cairo_context.set_source_surface(self._cairo_surface, 0, y) |
||
| 251 | # Clip to ensure only dst is affected by the change |
||
| 252 | self._mask_region(dst_top, dst_bot, left, right) |
||
| 253 | # Do the move |
||
| 254 | self._cairo_context.paint() |
||
| 255 | self._cairo_context.restore() |
||
| 256 | # Clear the emptied region |
||
| 257 | self._clear_region(clr_top, clr_bot, left, right) |
||
| 258 | self._screen.scroll(count) |
||
| 259 | |||
| 260 | def _nvim_highlight_set(self, attrs): |
||
| 261 | self._attrs = self._get_pango_attrs(attrs) |
||
| 262 | |||
| 263 | def _nvim_put(self, text): |
||
| 264 | if self._screen.row != self._pending[0]: |
||
| 265 | # flush pending text if jumped to a different row |
||
| 266 | self._flush() |
||
| 267 | # work around some redraw glitches that can happen |
||
| 268 | self._redraw_glitch_fix() |
||
| 269 | # Update internal screen |
||
| 270 | self._screen.put(self._get_pango_text(text), self._attrs) |
||
| 271 | self._pending[1] = min(self._screen.col - 1, self._pending[1]) |
||
| 272 | self._pending[2] = max(self._screen.col, self._pending[2]) |
||
| 273 | |||
| 274 | def _nvim_bell(self): |
||
| 275 | self._window.get_window().beep() |
||
| 276 | |||
| 277 | def _nvim_visual_bell(self): |
||
| 278 | pass |
||
| 279 | |||
| 280 | def _nvim_update_fg(self, fg): |
||
| 281 | self._foreground = fg |
||
| 282 | self._reset_cache() |
||
| 283 | |||
| 284 | def _nvim_update_bg(self, bg): |
||
| 285 | self._background = bg |
||
| 286 | self._reset_cache() |
||
| 287 | |||
| 288 | def _nvim_suspend(self): |
||
| 289 | self._window.iconify() |
||
| 290 | |||
| 291 | def _nvim_set_title(self, title): |
||
| 292 | self._window.set_title(title) |
||
| 293 | |||
| 294 | def _nvim_set_icon(self, icon): |
||
| 295 | self._window.set_icon_name(icon) |
||
| 296 | |||
| 297 | def _gtk_draw(self, wid, cr): |
||
| 298 | if not self._screen: |
||
| 299 | return |
||
| 300 | # from random import random |
||
| 301 | # cr.rectangle(0, 0, self._pixel_width, self._pixel_height) |
||
| 302 | # cr.set_source_rgb(random(), random(), random()) |
||
| 303 | # cr.fill() |
||
| 304 | self._cairo_surface.flush() |
||
| 305 | cr.save() |
||
| 306 | cr.rectangle(0, 0, self._pixel_width, self._pixel_height) |
||
| 307 | cr.clip() |
||
| 308 | cr.set_source_surface(self._cairo_surface, 0, 0) |
||
| 309 | cr.paint() |
||
| 310 | cr.restore() |
||
| 311 | if not self._busy and self._blink: |
||
| 312 | # Cursor is drawn separately in the window. This approach is |
||
| 313 | # simpler because it doesn't taint the internal cairo surface, |
||
| 314 | # which is used for scrolling |
||
| 315 | row, col = self._screen.row, self._screen.col |
||
| 316 | text, attrs = self._screen.get_cursor() |
||
| 317 | self._pango_draw(row, col, [(text, attrs,)], cr=cr, cursor=True) |
||
| 318 | x, y = self._get_coords(row, col) |
||
| 319 | currect = Rectangle(x, y, self._cell_pixel_width, |
||
| 320 | self._cell_pixel_height) |
||
| 321 | self._im_context.set_cursor_location(currect) |
||
| 322 | |||
| 323 | def _gtk_configure(self, widget, event): |
||
| 324 | def resize(*args): |
||
| 325 | self._resize_timer_id = None |
||
| 326 | width, height = self._window.get_size() |
||
| 327 | columns = width // self._cell_pixel_width |
||
| 328 | rows = height // self._cell_pixel_height |
||
| 329 | if self._screen.columns == columns and self._screen.rows == rows: |
||
| 330 | return |
||
| 331 | self._bridge.resize(columns, rows) |
||
| 332 | |||
| 333 | if not self._screen: |
||
| 334 | return |
||
| 335 | if event.width == self._pixel_width and \ |
||
| 336 | event.height == self._pixel_height: |
||
| 337 | return |
||
| 338 | if self._resize_timer_id is not None: |
||
| 339 | GLib.source_remove(self._resize_timer_id) |
||
| 340 | self._resize_timer_id = GLib.timeout_add(250, resize) |
||
| 341 | |||
| 342 | def _gtk_quit(self, *args): |
||
| 343 | self._bridge.exit() |
||
| 344 | |||
| 345 | def _gtk_key(self, widget, event, *args): |
||
| 346 | # This function was adapted from pangoterm source code |
||
| 347 | keyval = event.keyval |
||
| 348 | state = event.state |
||
| 349 | # GtkIMContext will eat a Shift-Space and not tell us about shift. |
||
| 350 | # Also don't let IME eat any GDK_KEY_KP_ events |
||
| 351 | done = (False if state & SHIFT and keyval == ord(' ') else |
||
| 352 | False if Gdk.KEY_KP_Space <= keyval <= Gdk.KEY_KP_Divide else |
||
| 353 | self._im_context.filter_keypress(event)) |
||
| 354 | if done: |
||
| 355 | # input method handled keypress |
||
| 356 | return True |
||
| 357 | if event.is_modifier: |
||
| 358 | # We don't need to track the state of modifier bits |
||
| 359 | return |
||
| 360 | # translate keyval to nvim key |
||
| 361 | key_name = Gdk.keyval_name(keyval) |
||
| 362 | if key_name.startswith('KP_'): |
||
| 363 | key_name = key_name[3:] |
||
| 364 | input_str = _stringify_key(KEY_TABLE.get(key_name, key_name), state) |
||
| 365 | self._bridge.input(input_str) |
||
| 366 | |||
| 367 | def _gtk_key_release(self, widget, event, *args): |
||
| 368 | self._im_context.filter_keypress(event) |
||
| 369 | |||
| 370 | View Code Duplication | def _gtk_button_press(self, widget, event, *args): |
|
| 371 | if not self._mouse_enabled or event.type != Gdk.EventType.BUTTON_PRESS: |
||
| 372 | return |
||
| 373 | button = 'Left' |
||
| 374 | if event.button == 2: |
||
| 375 | button = 'Middle' |
||
| 376 | elif event.button == 3: |
||
| 377 | button = 'Right' |
||
| 378 | col = int(math.floor(event.x / self._cell_pixel_width)) |
||
| 379 | row = int(math.floor(event.y / self._cell_pixel_height)) |
||
| 380 | input_str = _stringify_key(button + 'Mouse', event.state) |
||
| 381 | input_str += '<{0},{1}>'.format(col, row) |
||
| 382 | self._bridge.input(input_str) |
||
| 383 | self._pressed = button |
||
| 384 | |||
| 385 | def _gtk_button_release(self, widget, event, *args): |
||
| 386 | self._pressed = None |
||
| 387 | |||
| 388 | def _gtk_motion_notify(self, widget, event, *args): |
||
| 389 | if not self._mouse_enabled or not self._pressed: |
||
| 390 | return |
||
| 391 | col = int(math.floor(event.x / self._cell_pixel_width)) |
||
| 392 | row = int(math.floor(event.y / self._cell_pixel_height)) |
||
| 393 | input_str = _stringify_key(self._pressed + 'Drag', event.state) |
||
| 394 | input_str += '<{0},{1}>'.format(col, row) |
||
| 395 | self._bridge.input(input_str) |
||
| 396 | |||
| 397 | View Code Duplication | def _gtk_scroll(self, widget, event, *args): |
|
| 398 | if not self._mouse_enabled: |
||
| 399 | return |
||
| 400 | col = int(math.floor(event.x / self._cell_pixel_width)) |
||
| 401 | row = int(math.floor(event.y / self._cell_pixel_height)) |
||
| 402 | if event.direction == Gdk.ScrollDirection.UP: |
||
| 403 | key = 'ScrollWheelUp' |
||
| 404 | elif event.direction == Gdk.ScrollDirection.DOWN: |
||
| 405 | key = 'ScrollWheelDown' |
||
| 406 | else: |
||
| 407 | return |
||
| 408 | input_str = _stringify_key(key, event.state) |
||
| 409 | input_str += '<{0},{1}>'.format(col, row) |
||
| 410 | self._bridge.input(input_str) |
||
| 411 | |||
| 412 | def _gtk_focus_in(self, *a): |
||
| 413 | self._im_context.focus_in() |
||
| 414 | |||
| 415 | def _gtk_focus_out(self, *a): |
||
| 416 | self._im_context.focus_out() |
||
| 417 | |||
| 418 | def _gtk_input(self, widget, input_str, *args): |
||
| 419 | self._bridge.input(input_str.replace('<', '<lt>')) |
||
| 420 | |||
| 421 | def _start_blinking(self): |
||
| 422 | def blink(*args): |
||
| 423 | self._blink = not self._blink |
||
| 424 | self._screen_invalid() |
||
| 425 | self._blink_timer_id = GLib.timeout_add(500, blink) |
||
| 426 | if self._blink_timer_id: |
||
| 427 | GLib.source_remove(self._blink_timer_id) |
||
| 428 | self._blink = False |
||
| 429 | blink() |
||
| 430 | |||
| 431 | def _clear_region(self, top, bot, left, right): |
||
| 432 | self._flush() |
||
| 433 | self._cairo_context.save() |
||
| 434 | self._mask_region(top, bot, left, right) |
||
| 435 | r, g, b = _split_color(self._background) |
||
| 436 | r, g, b = r / 255.0, g / 255.0, b / 255.0 |
||
| 437 | self._cairo_context.set_source_rgb(r, g, b) |
||
| 438 | self._cairo_context.paint() |
||
| 439 | self._cairo_context.restore() |
||
| 440 | |||
| 441 | def _mask_region(self, top, bot, left, right, cr=None): |
||
| 442 | if not cr: |
||
| 443 | cr = self._cairo_context |
||
| 444 | x1, y1, x2, y2 = self._get_rect(top, bot, left, right) |
||
| 445 | cr.rectangle(x1, y1, x2 - x1, y2 - y1) |
||
| 446 | cr.clip() |
||
| 447 | |||
| 448 | def _get_rect(self, top, bot, left, right): |
||
| 449 | x1, y1 = self._get_coords(top, left) |
||
| 450 | x2, y2 = self._get_coords(bot, right) |
||
| 451 | return x1, y1, x2, y2 |
||
| 452 | |||
| 453 | def _get_coords(self, row, col): |
||
| 454 | x = col * self._cell_pixel_width |
||
| 455 | y = row * self._cell_pixel_height |
||
| 456 | return x, y |
||
| 457 | |||
| 458 | def _flush(self): |
||
| 459 | row, startcol, endcol = self._pending |
||
| 460 | self._pending[0] = self._screen.row |
||
| 461 | self._pending[1] = self._screen.col |
||
| 462 | self._pending[2] = self._screen.col |
||
| 463 | if startcol == endcol: |
||
| 464 | return |
||
| 465 | self._cairo_context.save() |
||
| 466 | ccol = startcol |
||
| 467 | buf = [] |
||
| 468 | bold = False |
||
| 469 | for _, col, text, attrs in self._screen.iter(row, row, startcol, |
||
| 470 | endcol - 1): |
||
| 471 | newbold = attrs and 'bold' in attrs[0] |
||
| 472 | if newbold != bold or not text: |
||
| 473 | if buf: |
||
| 474 | self._pango_draw(row, ccol, buf) |
||
| 475 | bold = newbold |
||
| 476 | buf = [(text, attrs,)] |
||
| 477 | ccol = col |
||
| 478 | else: |
||
| 479 | buf.append((text, attrs,)) |
||
| 480 | if buf: |
||
| 481 | self._pango_draw(row, ccol, buf) |
||
| 482 | self._cairo_context.restore() |
||
| 483 | |||
| 484 | def _pango_draw(self, row, col, data, cr=None, cursor=False): |
||
| 485 | markup = [] |
||
| 486 | for text, attrs in data: |
||
| 487 | if not attrs: |
||
| 488 | attrs = self._get_pango_attrs(None) |
||
| 489 | attrs = attrs[1] if cursor else attrs[0] |
||
| 490 | markup.append('<span {0}>{1}</span>'.format(attrs, text)) |
||
| 491 | markup = ''.join(markup) |
||
| 492 | self._pango_layout.set_markup(markup, -1) |
||
| 493 | # Draw the text |
||
| 494 | if not cr: |
||
| 495 | cr = self._cairo_context |
||
| 496 | x, y = self._get_coords(row, col) |
||
| 497 | if cursor and self._insert_cursor: |
||
| 498 | cr.rectangle(x, y, self._cell_pixel_width / 4, |
||
| 499 | self._cell_pixel_height) |
||
| 500 | cr.clip() |
||
| 501 | cr.move_to(x, y) |
||
| 502 | PangoCairo.update_layout(cr, self._pango_layout) |
||
| 503 | PangoCairo.show_layout(cr, self._pango_layout) |
||
| 504 | _, r = self._pango_layout.get_pixel_extents() |
||
| 505 | |||
| 506 | def _get_pango_text(self, text): |
||
| 507 | rv = self._pango_text_cache.get(text, None) |
||
| 508 | if rv is None: |
||
| 509 | rv = GLib.markup_escape_text(text or '') |
||
| 510 | self._pango_text_cache[text] = rv |
||
| 511 | return rv |
||
| 512 | |||
| 513 | def _get_pango_attrs(self, attrs): |
||
| 514 | key = tuple(sorted((k, v,) for k, v in (attrs or {}).items())) |
||
| 515 | rv = self._pango_attrs_cache.get(key, None) |
||
| 516 | if rv is None: |
||
| 517 | fg = self._foreground if self._foreground != -1 else 0 |
||
| 518 | bg = self._background if self._background != -1 else 0xffffff |
||
| 519 | n = { |
||
| 520 | 'foreground': _split_color(fg), |
||
| 521 | 'background': _split_color(bg), |
||
| 522 | } |
||
| 523 | if attrs: |
||
| 524 | # make sure that foreground and background are assigned first |
||
| 525 | for k in ['foreground', 'background']: |
||
| 526 | if k in attrs: |
||
| 527 | n[k] = _split_color(attrs[k]) |
||
| 528 | for k, v in attrs.items(): |
||
| 529 | if k == 'reverse': |
||
| 530 | n['foreground'], n['background'] = \ |
||
| 531 | n['background'], n['foreground'] |
||
| 532 | elif k == 'italic': |
||
| 533 | n['font_style'] = 'italic' |
||
| 534 | elif k == 'bold': |
||
| 535 | n['font_weight'] = 'bold' |
||
| 536 | if self._bold_spacing: |
||
| 537 | n['letter_spacing'] = str(self._bold_spacing) |
||
| 538 | elif k == 'underline': |
||
| 539 | n['underline'] = 'single' |
||
| 540 | c = dict(n) |
||
| 541 | c['foreground'] = _invert_color(*_split_color(fg)) |
||
| 542 | c['background'] = _invert_color(*_split_color(bg)) |
||
| 543 | c['foreground'] = _stringify_color(*c['foreground']) |
||
| 544 | c['background'] = _stringify_color(*c['background']) |
||
| 545 | n['foreground'] = _stringify_color(*n['foreground']) |
||
| 546 | n['background'] = _stringify_color(*n['background']) |
||
| 547 | n = ' '.join(['{0}="{1}"'.format(k, v) for k, v in n.items()]) |
||
| 548 | c = ' '.join(['{0}="{1}"'.format(k, v) for k, v in c.items()]) |
||
| 549 | rv = (n, c,) |
||
| 550 | self._pango_attrs_cache[key] = rv |
||
| 551 | return rv |
||
| 552 | |||
| 553 | def _reset_cache(self): |
||
| 554 | self._pango_text_cache = {} |
||
| 555 | self._pango_attrs_cache = {} |
||
| 556 | |||
| 557 | def _redraw_glitch_fix(self): |
||
| 558 | row, col = self._screen.row, self._screen.col |
||
| 559 | text, attrs = self._screen.get_cursor() |
||
| 560 | # when updating cells in italic or bold words, the result can become |
||
| 561 | # messy(characters can be clipped or leave remains when removed). To |
||
| 562 | # prevent that, always update non empty sequences of cells and the |
||
| 563 | # surrounding space. |
||
| 564 | # find the start of the sequence |
||
| 565 | lcol = col - 1 |
||
| 566 | while lcol >= 0: |
||
| 567 | text, _ = self._screen.get_cell(row, lcol) |
||
| 568 | lcol -= 1 |
||
| 569 | if text == ' ': |
||
| 570 | break |
||
| 571 | self._pending[1] = min(lcol + 1, self._pending[1]) |
||
| 572 | # find the end of the sequence |
||
| 573 | rcol = col + 1 |
||
| 574 | while rcol < self._screen.columns: |
||
| 575 | text, _ = self._screen.get_cell(row, rcol) |
||
| 576 | rcol += 1 |
||
| 577 | if text == ' ': |
||
| 578 | break |
||
| 579 | self._pending[2] = max(rcol, self._pending[2]) |
||
| 580 | |||
| 620 |
This can be caused by one of the following:
1. Missing Dependencies
This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.
2. Missing __init__.py files
This error could also result from missing
__init__.pyfiles in your module folders. Make sure that you place one file in each sub-folder.