diff --git a/examples/terminal/powerline.py b/examples/terminal/pl.py similarity index 53% rename from examples/terminal/powerline.py rename to examples/terminal/pl.py index dafbf340..cdecdaec 100755 --- a/examples/terminal/powerline.py +++ b/examples/terminal/pl.py @@ -7,16 +7,17 @@ import os import sys sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) -from lib.core import Powerline, mksegment -from lib.renderers import TerminalSegmentRenderer +from powerline.core import Powerline +from powerline.segment import mksegment +from powerline.ext.terminal import TerminalRenderer powerline = Powerline([ - mksegment('⭤ SSH', 220, 166, attr=Powerline.ATTR_BOLD), + mksegment('⭤ SSH', 220, 166, attr=TerminalRenderer.ATTR_BOLD), mksegment('username', 153, 31), mksegment('~', 248, 239), mksegment('projects', 248, 239), - mksegment('powerline', 231, 239, attr=Powerline.ATTR_BOLD), + mksegment('powerline', 231, 239, attr=TerminalRenderer.ATTR_BOLD), mksegment(filler=True), ]) -print(powerline.render(TerminalSegmentRenderer())) +print(powerline.render(TerminalRenderer)) diff --git a/examples/tmux/powerline.py b/examples/tmux/pl.py similarity index 55% rename from examples/tmux/powerline.py rename to examples/tmux/pl.py index 7f0a5da5..060fdbf3 100755 --- a/examples/tmux/powerline.py +++ b/examples/tmux/pl.py @@ -9,15 +9,16 @@ import os import sys sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) -from lib.core import Powerline, mksegment -from lib.renderers import TmuxSegmentRenderer +from powerline.core import Powerline +from powerline.segment import mksegment +from powerline.ext.tmux import TmuxRenderer powerline = Powerline([ - mksegment('⭤ SSH', 220, 166, attr=Powerline.ATTR_BOLD), + mksegment('⭤ SSH', 220, 166, attr=TmuxRenderer.ATTR_BOLD), mksegment('username', 153, 31), mksegment('23:45', 248, 239), - mksegment('10.0.0.110', 231, 239, attr=Powerline.ATTR_BOLD), + mksegment('10.0.0.110', 231, 239, attr=TmuxRenderer.ATTR_BOLD), mksegment(filler=True, cterm_fg=236, cterm_bg=236), ]) -print(powerline.render(TmuxSegmentRenderer()).encode('utf-8')) +print(powerline.render(TmuxRenderer).encode('utf-8')) diff --git a/examples/vim/powerline.py b/examples/vim/powerline.py deleted file mode 100644 index 42a35e11..00000000 --- a/examples/vim/powerline.py +++ /dev/null @@ -1,197 +0,0 @@ -# -*- coding: utf-8 -*- - -import vim -import os - -from lib.core import Powerline, mksegment -from lib.renderers import VimSegmentRenderer - -modes = { - 'n': 'NORMAL', - 'no': 'N·OPER', - 'v': 'VISUAL', - 'V': 'V·LINE', - '': 'V·BLCK', - 's': 'SELECT', - 'S': 'S·LINE', - '': 'S·BLCK', - 'i': 'INSERT', - 'R': 'REPLACE', - 'Rv': 'V·RPLCE', - 'c': 'COMMND', - 'cv': 'VIM EX', - 'ce': 'EX', - 'r': 'PROMPT', - 'rm': 'MORE', - 'r?': 'CONFIRM', - '!': 'SHELL', -} - -# We need to replace this private use glyph with a double-percent later -percent_placeholder = u'' - -if hasattr(vim, 'bindeval'): - # This branch is used to avoid invoking vim parser as much as possible - - def get_vim_func(f, rettype=None): - try: - return vim.bindeval('function("' + f + '")') - except vim.error: - return None - - vim_globals = vim.bindeval('g:') - - def set_global_var(var, val): - vim_globals[var] = val -else: - import json - - class VimFunc(object): - __slots__ = ('f', 'rettype') - - def __init__(self, f, rettype=None): - self.f = f - self.rettype = rettype - - def __call__(self, *args): - r = vim.eval(self.f + '(' + json.dumps(args)[1:-1] + ')') - if self.rettype: - return self.rettype(r) - return r - - get_vim_func = VimFunc - - def set_global_var(var, val): # NOQA - vim.command('let g:{0}={1}'.format(var, json.dumps(val))) - -vim_funcs = { - 'winwidth': get_vim_func('winwidth', rettype=int), - 'mode': get_vim_func('mode'), - 'fghead': get_vim_func('fugitive#head'), - 'line': get_vim_func('line', rettype=int), - 'col': get_vim_func('col', rettype=int), - 'expand': get_vim_func('expand'), - 'tbcurtag': get_vim_func('tagbar#currenttag'), - 'hlexists': get_vim_func('hlexists', rettype=int), -} - -getwinvar = get_vim_func('getwinvar') -setwinvar = get_vim_func('setwinvar') - - -def statusline(winnr): - winwidth = vim_funcs['winwidth'](winnr) - - current = getwinvar(winnr, 'current') - windata = getwinvar(winnr, 'powerline') - - if current: - # Recreate segment data for each redraw if we're in the current window - line_current = vim_funcs['line']('.') - line_end = vim_funcs['line']('$') - line_percent = line_current * 100 // line_end - - try: - branch = vim_funcs['fghead'](5) - except vim.error: - vim_funcs['fghead'] = None - branch = '' - except TypeError: - branch = '' - if branch: - branch = u'⭠ ' + branch - - # Fun gradient colored percent segment - line_percent_gradient = [160, 166, 172, 178, 184, 190] - line_percent_color = line_percent_gradient[int((len(line_percent_gradient) - 1) * line_percent / 100)] - - col_current = vim_funcs['col']('.') - - filepath, filename = os.path.split(vim_funcs['expand']('%:~:.')) - filename_color = 231 - if filepath: - filepath += os.sep - - if not filename: - filename = '[No Name]' - filename_color = 250 - - readonly = vim.eval('&ro ? "⭤ " : ""') - modified = vim.eval('&mod ? " +" : ""') - - try: - currenttag = vim_funcs['tbcurtag']('%s', '') - except vim.error: - vim_funcs['tbcurtag'] = None - currenttag = '' - except TypeError: - currenttag = '' - - windata = { - 'paste': vim.eval('&paste ? "PASTE" : ""'), - 'branch': branch, - 'readonly': readonly, - 'filepath': filepath, - 'filename': filename, - 'filename_color': filename_color, - 'modified': modified, - 'currenttag': currenttag, - 'fileformat': vim.eval('&ff'), - 'fileencoding': vim.eval('&fenc'), - 'filetype': vim.eval('&ft'), - 'line_percent': str(line_percent).rjust(3) + percent_placeholder, - 'line_percent_color': line_percent_color, - 'linecurrent': str(line_current).rjust(3), - 'colcurrent': ':' + str(col_current).ljust(2), - } - - setwinvar(winnr, 'powerline', windata) - - mode = modes[vim_funcs['mode']()] - - if not current: - mode = None - - powerline = Powerline([ - mksegment(mode, 22, 148, attr=Powerline.ATTR_BOLD), - mksegment(windata['paste'], 231, 166, attr=Powerline.ATTR_BOLD), - mksegment(windata['branch'], 250, 240, priority=60), - mksegment(windata['readonly'], 196, 240, draw_divider=False), - mksegment(windata['filepath'], 250, 240, draw_divider=False, priority=40), - mksegment(windata['filename'], windata['filename_color'], 240, attr=Powerline.ATTR_BOLD, draw_divider=False), - mksegment(windata['modified'], 220, 240, attr=Powerline.ATTR_BOLD), - mksegment(windata['currenttag'], 246, 236, draw_divider=False, priority=100), - mksegment(filler=True, cterm_fg=236, cterm_bg=236), - mksegment(windata['fileformat'], 247, 236, side='r', priority=50, draw_divider=False), - mksegment(windata['fileencoding'], 247, 236, side='r', priority=50), - mksegment(windata['filetype'], 247, 236, side='r', priority=50), - mksegment(windata['line_percent'], windata['line_percent_color'], 240, side='r', priority=30), - mksegment(u'⭡ ', 239, 252, side='r'), - mksegment(windata['linecurrent'], 235, 252, attr=Powerline.ATTR_BOLD, side='r', draw_divider=False), - mksegment(windata['colcurrent'], 244, 252, side='r', priority=30, draw_divider=False), - ]) - - renderer = VimSegmentRenderer() - stl = powerline.render(renderer, winwidth) - - # Replace percent placeholders - stl = stl.replace(percent_placeholder, '%%') - - # Create highlighting groups - for idx, hl in renderer.hl_groups.items(): - if vim_funcs['hlexists'](hl['name']): - # Only create hl group if it doesn't already exist - continue - - vim.command('hi {group} ctermfg={ctermfg} guifg={guifg} guibg={guibg} ctermbg={ctermbg} cterm={attr} gui={attr}'.format( - group=hl['name'], - ctermfg=hl['ctermfg'], - guifg='#{0:06x}'.format(hl['guifg']) if hl['guifg'] != 'NONE' else 'NONE', - ctermbg=hl['ctermbg'], - guibg='#{0:06x}'.format(hl['guibg']) if hl['guibg'] != 'NONE' else 'NONE', - attr=','.join(hl['attr']), - )) - - return stl - -# vim: ft=python ts=4 sts=4 sw=4 noet diff --git a/examples/vim/powerline.vim b/examples/vim/powerline.vim index 4babdcb8..093636e1 100644 --- a/examples/vim/powerline.vim +++ b/examples/vim/powerline.vim @@ -3,7 +3,9 @@ python import sys, vim, os python sys.path.append(vim.eval('expand(":h:h:h")')) -python from examples.vim.powerline import statusline +python from examples.vim.pl import statusline +python from powerline.core import Powerline +python pl = Powerline('vim') if exists('*pyeval') let s:pyeval = function('pyeval') @@ -15,7 +17,7 @@ else endif function! Powerline(winnr) - return s:pyeval('statusline('. a:winnr .')') + return s:pyeval('pl.renderer.render('. a:winnr .')') endfunction function! s:WinDoPowerline() diff --git a/lib/core.py b/lib/core.py deleted file mode 100644 index d3c407d6..00000000 --- a/lib/core.py +++ /dev/null @@ -1,162 +0,0 @@ -# -*- coding: utf-8 -*- - -from lib.colors import cterm_to_hex - - -class Powerline(object): - ATTR_BOLD = 1 - ATTR_ITALIC = 2 - ATTR_UNDERLINE = 4 - - dividers = { - 'l': { - 'hard': u'⮀', - 'soft': u'⮁', - }, - 'r': { - 'hard': u'⮂', - 'soft': u'⮃', - }, - } - - def __init__(self, segments): - '''Create a new Powerline. - - Segments that have empty contents and aren't filler segments are - dropped from the segment array. - ''' - self.segments = [segment for segment in segments if segment['contents'] or segment['filler']] - self._hl = {} - - def render(self, renderer, width=None): - '''Render all the segments with the specified renderer. - - This method loops through the segment array and compares the - foreground/background colors and divider properties and returns the - rendered statusline as a string. - - When a width is provided, low-priority segments are dropped one at - a time until the line is shorter than the width, or only segments - with a negative priority are left. If one or more filler segments are - provided they will fill the remaining space until the desired width is - reached. - ''' - def render_segments(segments, render_highlighted=True): - '''Render a segment array. - - By default this function renders both raw (un-highlighted segments - used for calculating final width) and highlighted segments. The raw - rendering is used for calculating the total width for dropping - low-priority segments. - ''' - rendered_highlighted = u'' - segments_len = len(segments) - empty_segment = mksegment() - - for idx, segment in enumerate(segments): - prev = segments[idx - 1] if idx > 0 else empty_segment - next = segments[idx + 1] if idx < segments_len - 1 else empty_segment - - segment['rendered_raw'] = u'' - compare = next if segment['side'] == 'l' else prev - outer_padding = ' ' if idx == 0 or idx == segments_len - 1 else '' - divider_type = 'soft' if compare['bg'] == segment['bg'] else 'hard' - divider = self.dividers[segment['side']][divider_type] - divider_hl = '' - segment_hl = '' - - if render_highlighted: - # Generate and cache renderer highlighting - if divider_type == 'hard': - hl_key = (segment['bg'], compare['bg']) - if not hl_key in self._hl: - self._hl[hl_key] = renderer.hl(*hl_key) - divider_hl = self._hl[hl_key] - - hl_key = (segment['fg'], segment['bg'], segment['attr']) - if not hl_key in self._hl: - self._hl[hl_key] = renderer.hl(*hl_key) - segment_hl = self._hl[hl_key] - - if segment['filler']: - # Filler segments shouldn't be padded - rendered_highlighted += segment['contents'] - elif segment['draw_divider'] or divider_type == 'hard': - # Draw divider if specified, or if it's a hard divider - # Note: Hard dividers are always drawn, regardless of - # the draw_divider option - if segment['side'] == 'l': - segment['rendered_raw'] += outer_padding + segment['contents'] + ' ' + divider + ' ' - rendered_highlighted += segment_hl + outer_padding + segment['contents'] + ' ' + divider_hl + divider + ' ' - else: - segment['rendered_raw'] += ' ' + divider + ' ' + segment['contents'] + outer_padding - rendered_highlighted += ' ' + divider_hl + divider + segment_hl + ' ' + segment['contents'] + outer_padding - elif segment['contents']: - # Segments without divider - if segment['side'] == 'l': - segment['rendered_raw'] += outer_padding + segment['contents'] - rendered_highlighted += segment_hl + outer_padding + segment['contents'] - else: - segment['rendered_raw'] += segment['contents'] + outer_padding - rendered_highlighted += segment_hl + segment['contents'] + outer_padding - else: - # Unknown segment type, skip it - continue - - return rendered_highlighted - - rendered_highlighted = render_segments(self.segments) - - if not width: - # No width specified, so we don't need to crop or pad anything - return rendered_highlighted - - # Create an ordered list of segments that can be dropped - segments_priority = [segment for segment in sorted(self.segments, key=lambda segment: segment['priority'], reverse=True) if segment['priority'] > 0] - - while self._total_len() > width and len(segments_priority): - self.segments.remove(segments_priority[0]) - segments_priority.pop(0) - - # Do another render pass so we can calculate the correct amount of filler space - render_segments(self.segments) - - # Distribute the remaining space on the filler segments - segments_fillers = [segment for segment in self.segments if segment['filler'] is True] - if segments_fillers: - segments_fillers_len, segments_fillers_remainder = divmod((width - self._total_len()), len(segments_fillers)) - segments_fillers_contents = ' ' * segments_fillers_len - for segment in segments_fillers: - segment['contents'] = segments_fillers_contents - # Add remainder whitespace to the first filler segment - segments_fillers[0]['contents'] += ' ' * segments_fillers_remainder - - return render_segments(self.segments) - - def _total_len(self): - '''Return total/rendered length of all segments. - - This method uses the rendered_raw property of the segments and requires - that the segments have been rendered using the render() method first. - ''' - return len(''.join([segment['rendered_raw'] for segment in self.segments])) - - -def mksegment(contents=None, cterm_fg=False, cterm_bg=False, attr=False, hex_fg=False, hex_bg=False, side='l', draw_divider=True, priority=-1, filler=False): - '''Convenience wrapper for segment generation. - ''' - try: - contents = unicode(contents or u'') - except UnicodeDecodeError: - contents = contents.decode('utf-8') or u'' - - return { - 'contents': contents, - 'fg': (cterm_fg, hex_fg or cterm_to_hex.get(cterm_fg, 0xffffff)), - 'bg': (cterm_bg, hex_bg or cterm_to_hex.get(cterm_bg, 0x000000)), - 'attr': attr, - 'side': side, - 'draw_divider': False if filler else draw_divider, - 'priority': priority, - 'filler': filler, - } diff --git a/lib/renderers/__init__.py b/lib/renderers/__init__.py deleted file mode 100644 index 3c7c2387..00000000 --- a/lib/renderers/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -class SegmentRenderer: - def hl(self, fg=None, bg=None, attr=None): - raise NotImplementedError - -from lib.renderers.terminal import TerminalSegmentRenderer -from lib.renderers.tmux import TmuxSegmentRenderer -from lib.renderers.vim import VimSegmentRenderer diff --git a/lib/renderers/vim.py b/lib/renderers/vim.py deleted file mode 100644 index 07a4dbb2..00000000 --- a/lib/renderers/vim.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python - -from lib.core import Powerline -from lib.renderers import SegmentRenderer - - -class VimSegmentRenderer(SegmentRenderer): - '''Powerline vim segment renderer. - ''' - def __init__(self): - self.hl_groups = {} - - def hl(self, fg=None, bg=None, attr=None): - '''Highlight a segment. - - If an argument is None, the argument is ignored. If an argument is - False, the argument is reset to the terminal defaults. If an argument - is a valid color or attribute, it's added to the vim highlight group. - ''' - # We don't need to explicitly reset attributes in vim, so skip those calls - if not attr and not bg and not fg: - return '' - - if not (fg, bg, attr) in self.hl_groups: - hl_group = { - 'ctermfg': 'NONE', - 'guifg': 'NONE', - 'ctermbg': 'NONE', - 'guibg': 'NONE', - 'attr': ['NONE'], - 'name': '', - } - - if fg is not None and fg is not False: - hl_group['ctermfg'] = fg[0] - hl_group['guifg'] = fg[1] - - if bg is not None and bg is not False: - hl_group['ctermbg'] = bg[0] - hl_group['guibg'] = bg[1] - - if attr: - hl_group['attr'] = [] - if attr & Powerline.ATTR_BOLD: - hl_group['attr'].append('bold') - if attr & Powerline.ATTR_ITALIC: - hl_group['attr'].append('italic') - if attr & Powerline.ATTR_UNDERLINE: - hl_group['attr'].append('underline') - - hl_group['name'] = 'Pl_' + \ - str(hl_group['ctermfg']) + '_' + \ - str(hl_group['guifg']) + '_' + \ - str(hl_group['ctermbg']) + '_' + \ - str(hl_group['guibg']) + '_' + \ - ''.join(hl_group['attr']) - - self.hl_groups[(fg, bg, attr)] = hl_group - - return '%#' + self.hl_groups[(fg, bg, attr)]['name'] + '#' diff --git a/lib/__init__.py b/powerline/__init__.py similarity index 100% rename from lib/__init__.py rename to powerline/__init__.py diff --git a/lib/colors.py b/powerline/colorscheme.py similarity index 51% rename from lib/colors.py rename to powerline/colorscheme.py index c0a34e45..7a2edd66 100644 --- a/lib/colors.py +++ b/powerline/colorscheme.py @@ -1,3 +1,105 @@ +# -*- coding: utf-8 -*- + + +class Colorscheme(object): + default_mode_key = '__default__' + + def __init__(self, colorscheme): + '''Initialize a colorscheme. + ''' + self.colors = {} + self.modes_groups = { + self.default_mode_key: {} + } + + # Create a dict of color tuples with both a cterm and hex value + for color_name, color in colorscheme['colors'].items(): + try: + self.colors[color_name] = (color[0], color[1]) + except TypeError: + self.colors[color_name] = (color, cterm_to_hex[color]) + + # Create highlighting groups for all modes + for group_name, group_props in colorscheme['groups'].items(): + group_attr_flag = self._get_attr_flag(group_props.get('attr', [])) + + self.modes_groups[self.default_mode_key][group_name] = { + 'fg': self.colors[group_props['fg']], + 'bg': self.colors[group_props['bg']], + 'attr': group_attr_flag, + } + + # Create mode-specific highlighting for this group + for mode, translations in colorscheme['mode_translations'].items(): + if not mode in self.modes_groups: + self.modes_groups[mode] = {} + + if group_name in translations['groups']: + # Override entire group if present in the translations group dict + self.modes_groups[mode][group_name] = { + 'fg': self.colors[translations['groups'][group_name]['fg']], + 'bg': self.colors[translations['groups'][group_name]['bg']], + 'attr': self._get_attr_flag(translations['groups'][group_name].get('attr', [])), + } + else: + # Fallback to color translations from the translations colors dict + self.modes_groups[mode][group_name] = { + 'fg': self.colors[translations['colors'].get(group_props['fg'], group_props['fg'])], + 'bg': self.colors[translations['colors'].get(group_props['bg'], group_props['bg'])], + 'attr': group_attr_flag, + } + + def get_group_highlighting(self, group): + '''Return highlighting information for all modes of a highlighting group. + ''' + group_highlighting = {} + for mode, mode_group in self.modes_groups.items(): + try: + group_highlighting[mode] = mode_group[group] + except TypeError: + for try_group in group: + if try_group in self.modes_groups[mode]: + group_highlighting[mode] = mode_group[try_group] + break + finally: + if mode not in group_highlighting: + raise KeyError('Highlighting groups not found in colorscheme: {0}'.format(group)) + return group_highlighting + + def get_highlighting(self, group, mode=None): + '''Return highlighting information for a highlighting group and mode. + + If no mode is specified, or the mode doesn't exist, highlighting for + the default mode is returned. + ''' + if not mode or mode not in self.modes_groups: + mode = self.default_mode_key + + try: + return self.modes_groups[mode][group] + except TypeError: + for try_group in group: + if try_group in self.modes_groups[mode]: + return self.modes_groups[mode][try_group] + raise KeyError('Highlighting groups not found in colorscheme: {0}'.format(group)) + + return self.modes_groups[mode][group] + + def _get_attr_flag(self, attributes): + '''Convert an attribute array to a renderer flag. + ''' + from powerline.renderer import Renderer + + attr_flag = 0 + if 'bold' in attributes: + attr_flag |= Renderer.ATTR_BOLD + if 'italic' in attributes: + attr_flag |= Renderer.ATTR_ITALIC + if 'underline' in attributes: + attr_flag |= Renderer.ATTR_UNDERLINE + + return attr_flag + cterm_to_hex = { 16: 0x000000, 17: 0x00005f, 18: 0x000087, 19: 0x0000af, 20: 0x0000d7, 21: 0x0000ff, 22: 0x005f00, 23: 0x005f5f, 24: 0x005f87, 25: 0x005faf, 26: 0x005fd7, 27: 0x005fff, diff --git a/powerline/colorschemes/default.json b/powerline/colorschemes/default.json new file mode 100644 index 00000000..bdcf2605 --- /dev/null +++ b/powerline/colorschemes/default.json @@ -0,0 +1,126 @@ +{ + "name": "default", + "colors": { + "black": 16, + "white": 231, + + "darkestgreen": 22, + "darkgreen": 28, + "mediumgreen": 70, + "brightgreen": 148, + + "darkestcyan": 23, + "mediumcyan": 117, + + "darkestblue": 24, + "darkblue": 31, + + "darkestred": 52, + "darkred": 88, + "mediumred": 124, + "brightred": 160, + "brightestred": 196, + + "darkestpurple": 55, + "mediumpurple": 98, + "brightpurple": 189, + + "mediumorange": 166, + "brightorange": 208, + "brightestorange": 214, + + "brightyellow": 220, + + "gray0": 233, + "gray1": 235, + "gray2": 236, + "gray3": 239, + "gray4": 240, + "gray5": 241, + "gray6": 244, + "gray7": 245, + "gray8": 247, + "gray9": 250, + "gray10": 252, + + "gradient1": 190, + "gradient2": 184, + "gradient3": 178, + "gradient4": 172, + "gradient5": 166, + "gradient6": 160 + }, + "groups": { + "background": { "fg": "white", "bg": "gray2" }, + "mode": { "fg": "darkestgreen", "bg": "brightgreen", "attr": ["bold"] }, + "modified_indicator": { "fg": "brightyellow", "bg": "gray4", "attr": ["bold"] }, + "paste_indicator": { "fg": "white", "bg": "mediumorange", "attr": ["bold"] }, + "readonly_indicator": { "fg": "brightestred", "bg": "gray4" }, + "branch": { "fg": "gray9", "bg": "gray4" }, + "file_directory": { "fg": "gray9", "bg": "gray4" }, + "file_name": { "fg": "white", "bg": "gray4", "attr": ["bold"] }, + "file_name_empty": { "fg": "gray9", "bg": "gray4" }, + "file_format": { "fg": "gray8", "bg": "gray2" }, + "file_encoding": { "fg": "gray8", "bg": "gray2" }, + "file_type": { "fg": "gray8", "bg": "gray2" }, + "line_percent": { "fg": "gray9", "bg": "gray4" }, + "line_percent_gradient1": { "fg": "gradient1", "bg": "gray4" }, + "line_percent_gradient2": { "fg": "gradient2", "bg": "gray4" }, + "line_percent_gradient3": { "fg": "gradient3", "bg": "gray4" }, + "line_percent_gradient4": { "fg": "gradient4", "bg": "gray4" }, + "line_percent_gradient5": { "fg": "gradient5", "bg": "gray4" }, + "line_percent_gradient6": { "fg": "gradient6", "bg": "gray4" }, + "line_current": { "fg": "gray1", "bg": "gray10", "attr": ["bold"] }, + "line_current_symbol": { "fg": "gray1", "bg": "gray10" }, + "col_current": { "fg": "gray6", "bg": "gray10" } + }, + "mode_translations": { + "nc": { + "colors": { + "gray0": "gray0", + "gray1": "gray0", + "gray2": "gray0", + "gray3": "gray1", + "gray4": "gray1", + "gray5": "gray1", + "gray6": "gray1", + "gray7": "gray4", + "gray8": "gray4", + "gray9": "gray4", + "gray10": "gray5", + "white": "gray6", + "gradient1": "gray5", + "gradient2": "gray5", + "gradient3": "gray5", + "gradient4": "gray5", + "gradient5": "gray5", + "gradient6": "gray5" + }, + "groups": {} + }, + "i": { + "colors": { + "gray0": "darkestblue", + "gray1": "darkestblue", + "gray2": "darkestblue", + "gray3": "darkblue", + "gray4": "darkblue", + "gray5": "darkestcyan", + "gray6": "darkestcyan", + "gray7": "darkestcyan", + "gray8": "mediumcyan", + "gray9": "mediumcyan", + "gray10": "mediumcyan", + "gradient1": "mediumcyan", + "gradient2": "mediumcyan", + "gradient3": "mediumcyan", + "gradient4": "mediumcyan", + "gradient5": "mediumcyan", + "gradient6": "mediumcyan" + }, + "groups": { + "mode": { "fg": "darkestcyan", "bg": "white", "attr": ["bold"] } + } + } + } +} diff --git a/powerline/config.json b/powerline/config.json new file mode 100644 index 00000000..a570fb6a --- /dev/null +++ b/powerline/config.json @@ -0,0 +1,20 @@ +{ + "common": { + "dividers": { + "left": { + "hard": "⮀", + "soft": "⮁" + }, + "right": { + "hard": "⮂", + "soft": "⮃" + } + } + }, + "ext": { + "vim": { + "colorscheme": "default", + "theme": "default" + } + } +} diff --git a/powerline/core.py b/powerline/core.py new file mode 100644 index 00000000..85dd36d1 --- /dev/null +++ b/powerline/core.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- + +import importlib +import json +import os +import sys + +from colorscheme import Colorscheme +from theme import Theme + + +class Powerline(object): + def __init__(self, ext): + try: + config_home = os.environ['XDG_CONFIG_HOME'] + except KeyError: + config_home = os.path.expanduser('~/.config') + + config_path = os.path.join(config_home, 'powerline') + plugin_path = os.path.realpath(os.path.dirname(__file__)) + self.search_paths = [config_path, plugin_path] + + sys.path[:0] = self.search_paths + + # Load main config file + config = self._load_json_config('config') + self.config = config['common'] + self.config_ext = config['ext'][ext] + + # Load and initialize colorscheme + colorscheme_config = self._load_json_config(os.path.join('colorschemes', self.config_ext['colorscheme'])) + colorscheme = Colorscheme(colorscheme_config) + + # Load and initialize extension theme + theme_config = self._load_json_config(os.path.join('themes', ext, self.config_ext['theme'])) + self.theme = Theme(ext, colorscheme, theme_config, self.config) + + # Load and initialize extension renderer + renderer_module_name = 'powerline.ext.{0}.renderer'.format(ext) + renderer_class_name = '{0}Renderer'.format(ext.capitalize()) + renderer_class = getattr(importlib.import_module(renderer_module_name), renderer_class_name) + self.renderer = renderer_class(self.theme) + + def _load_json_config(self, config_file): + config_file += '.json' + for path in self.search_paths: + config_file_path = os.path.join(path, config_file) + if os.path.isfile(config_file_path): + with open(config_file_path, 'rb') as config_file_fp: + return json.load(config_file_fp) + + raise IOError('Config file not found in search path: {0}'.format(config_file)) diff --git a/vim/__init__.py b/powerline/ext/__init__.py similarity index 100% rename from vim/__init__.py rename to powerline/ext/__init__.py diff --git a/powerline/ext/terminal/__init__.py b/powerline/ext/terminal/__init__.py new file mode 100644 index 00000000..5bf82fe6 --- /dev/null +++ b/powerline/ext/terminal/__init__.py @@ -0,0 +1 @@ +from renderer import TerminalRenderer # NOQA diff --git a/lib/renderers/terminal.py b/powerline/ext/terminal/renderer.py similarity index 79% rename from lib/renderers/terminal.py rename to powerline/ext/terminal/renderer.py index ff55dff2..0c5c5ebe 100644 --- a/lib/renderers/terminal.py +++ b/powerline/ext/terminal/renderer.py @@ -1,10 +1,9 @@ -#!/usr/bin/env python +# -*- coding: utf-8 -*- -from lib.core import Powerline -from lib.renderers import SegmentRenderer +from powerline.renderer import Renderer -class TerminalSegmentRenderer(SegmentRenderer): +class TerminalRenderer(Renderer): '''Powerline terminal segment renderer. ''' def hl(self, fg=None, bg=None, attr=None): @@ -32,7 +31,7 @@ class TerminalSegmentRenderer(SegmentRenderer): if attr is False: ansi += [22] else: - if attr & Powerline.ATTR_BOLD: + if attr & Renderer.ATTR_BOLD: ansi += [1] return '[{0}m'.format(';'.join(str(attr) for attr in ansi)) diff --git a/powerline/ext/tmux/__init__.py b/powerline/ext/tmux/__init__.py new file mode 100644 index 00000000..c8e23c53 --- /dev/null +++ b/powerline/ext/tmux/__init__.py @@ -0,0 +1 @@ +from renderer import TmuxRenderer # NOQA diff --git a/lib/renderers/tmux.py b/powerline/ext/tmux/renderer.py similarity index 71% rename from lib/renderers/tmux.py rename to powerline/ext/tmux/renderer.py index 07db9ff1..458cb191 100644 --- a/lib/renderers/tmux.py +++ b/powerline/ext/tmux/renderer.py @@ -1,10 +1,9 @@ -#!/usr/bin/env python +# -*- coding: utf-8 -*- -from lib.core import Powerline -from lib.renderers import SegmentRenderer +from powerline.renderer import Renderer -class TmuxSegmentRenderer(SegmentRenderer): +class TmuxRenderer(Renderer): '''Powerline tmux segment renderer. ''' def hl(self, fg=None, bg=None, attr=None): @@ -22,15 +21,15 @@ class TmuxSegmentRenderer(SegmentRenderer): if attr is False: tmux_attr += ['nobold', 'noitalics', 'nounderscore'] else: - if attr & Powerline.ATTR_BOLD: + if attr & Renderer.ATTR_BOLD: tmux_attr += ['bold'] else: tmux_attr += ['nobold'] - if attr & Powerline.ATTR_ITALIC: + if attr & Renderer.ATTR_ITALIC: tmux_attr += ['italics'] else: tmux_attr += ['noitalics'] - if attr & Powerline.ATTR_UNDERLINE: + if attr & Renderer.ATTR_UNDERLINE: tmux_attr += ['underscore'] else: tmux_attr += ['nounderscore'] diff --git a/powerline/ext/vim/__init__.py b/powerline/ext/vim/__init__.py new file mode 100644 index 00000000..69d94051 --- /dev/null +++ b/powerline/ext/vim/__init__.py @@ -0,0 +1 @@ +from renderer import VimRenderer # NOQA diff --git a/vim/bindings.py b/powerline/ext/vim/bindings.py similarity index 100% rename from vim/bindings.py rename to powerline/ext/vim/bindings.py diff --git a/powerline/ext/vim/renderer.py b/powerline/ext/vim/renderer.py new file mode 100644 index 00000000..19ee4d59 --- /dev/null +++ b/powerline/ext/vim/renderer.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- + +from powerline.ext.vim.bindings import vim_get_func +from powerline.renderer import Renderer + +import vim + +vim_mode = vim_get_func('mode') +vim_winwidth = vim_get_func('winwidth', rettype=int) +vim_getwinvar = vim_get_func('getwinvar') +vim_setwinvar = vim_get_func('setwinvar') + + +class VimRenderer(Renderer): + '''Powerline vim segment renderer. + ''' + PERCENT_PLACEHOLDER = u'' + + def __init__(self, theme): + super(VimRenderer, self).__init__(theme) + self.hl_groups = {} + + def render(self, winnr): + '''Render all segments. + + This method handles replacing of the percent placeholder for vim + statuslines, and it caches segment contents which are retrieved and + used in non-current windows. + ''' + current = vim_getwinvar(winnr, 'current') + winwidth = vim_winwidth(winnr) + + if current or not vim_getwinvar(winnr, 'powerline'): + contents_cached = {segment['key']: segment['contents'] for segment in self.segments if segment['type'] == 'function'} + vim_setwinvar(winnr, 'powerline', contents_cached) + + if current: + mode = vim_mode() + contents_override = None + else: + mode = 'nc' + contents_cached = vim_getwinvar(winnr, 'powerline') + contents_override = {k: contents_cached[k].decode('utf-8') for k in contents_cached.keys()} + + statusline = super(VimRenderer, self).render(mode, winwidth, contents_override) + statusline = statusline.replace(self.PERCENT_PLACEHOLDER, '%%') + + return statusline + + def hl(self, fg=None, bg=None, attr=None): + '''Highlight a segment. + + If an argument is None, the argument is ignored. If an argument is + False, the argument is reset to the terminal defaults. If an argument + is a valid color or attribute, it's added to the vim highlight group. + ''' + # We don't need to explicitly reset attributes in vim, so skip those calls + if not attr and not bg and not fg: + return '' + + if not (fg, bg, attr) in self.hl_groups: + hl_group = { + 'ctermfg': 'NONE', + 'guifg': 'NONE', + 'ctermbg': 'NONE', + 'guibg': 'NONE', + 'attr': ['NONE'], + 'name': '', + } + + if fg is not None and fg is not False: + hl_group['ctermfg'] = fg[0] + hl_group['guifg'] = fg[1] + + if bg is not None and bg is not False: + hl_group['ctermbg'] = bg[0] + hl_group['guibg'] = bg[1] + + if attr: + hl_group['attr'] = [] + if attr & self.ATTR_BOLD: + hl_group['attr'].append('bold') + if attr & self.ATTR_ITALIC: + hl_group['attr'].append('italic') + if attr & self.ATTR_UNDERLINE: + hl_group['attr'].append('underline') + + hl_group['name'] = 'Pl_' + \ + str(hl_group['ctermfg']) + '_' + \ + str(hl_group['guifg']) + '_' + \ + str(hl_group['ctermbg']) + '_' + \ + str(hl_group['guibg']) + '_' + \ + ''.join(hl_group['attr']) + + self.hl_groups[(fg, bg, attr)] = hl_group + + # Create highlighting group in vim + vim.command('hi {group} ctermfg={ctermfg} guifg={guifg} guibg={guibg} ctermbg={ctermbg} cterm={attr} gui={attr}'.format( + group=hl_group['name'], + ctermfg=hl_group['ctermfg'], + guifg='#{0:06x}'.format(hl_group['guifg']) if hl_group['guifg'] != 'NONE' else 'NONE', + ctermbg=hl_group['ctermbg'], + guibg='#{0:06x}'.format(hl_group['guibg']) if hl_group['guibg'] != 'NONE' else 'NONE', + attr=','.join(hl_group['attr']), + )) + + return '%#' + self.hl_groups[(fg, bg, attr)]['name'] + '#' diff --git a/powerline/ext/vim/segments/__init__.py b/powerline/ext/vim/segments/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/vim/segments/core.py b/powerline/ext/vim/segments/core.py similarity index 74% rename from vim/segments/core.py rename to powerline/ext/vim/segments/core.py index e5586d96..5de784b8 100644 --- a/vim/segments/core.py +++ b/powerline/ext/vim/segments/core.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- +import os import vim -from bindings import vim_get_func +from powerline.ext.vim.bindings import vim_get_func vim_funcs = { 'col': vim_get_func('col', rettype=int), @@ -15,24 +16,24 @@ vim_funcs = { } vim_modes = { - 'n': 'NORMAL', - 'no': 'N·OPER', - 'v': 'VISUAL', - 'V': 'V·LINE', - '': 'V·BLCK', - 's': 'SELECT', - 'S': 'S·LINE', - '': 'S·BLCK', - 'i': 'INSERT', - 'R': 'REPLACE', - 'Rv': 'V·RPLCE', - 'c': 'COMMND', - 'cv': 'VIM EX', - 'ce': 'EX', - 'r': 'PROMPT', - 'rm': 'MORE', - 'r?': 'CONFIRM', - '!': 'SHELL', + 'n': u'NORMAL', + 'no': u'N·OPER', + 'v': u'VISUAL', + 'V': u'V·LINE', + '': u'V·BLCK', + 's': u'SELECT', + 'S': u'S·LINE', + '': u'S·BLCK', + 'i': u'INSERT', + 'R': u'REPLACE', + 'Rv': u'V·RPLCE', + 'c': u'COMMND', + 'cv': u'VIM EX', + 'ce': u'EX', + 'r': u'PROMPT', + 'rm': u'MORE', + 'r?': u'CONFIRM', + '!': u'SHELL', } @@ -48,12 +49,12 @@ def mode(override=None): mode = vim_funcs['mode']() if not override: - return (mode, vim_modes[mode]) + return vim_modes[mode] try: - return (mode, override[mode]) + return override[mode] except IndexError: - return (mode, vim_modes[mode]) + return vim_modes[mode] def modified_indicator(text=u'+'): @@ -74,7 +75,7 @@ def readonly_indicator(text=u'⭤'): return text if int(vim.eval('&readonly')) else None -def branch(symbol=u'⭠'): +def branch(): '''Return VCS branch. TODO: Expand this function to handle several VCS plugins. @@ -87,13 +88,13 @@ def branch(symbol=u'⭠'): except TypeError: pass - return symbol + ' ' + branch if branch else None + return branch if branch else None def file_directory(): '''Return file directory (head component of the file path). ''' - return vim_funcs['expand']('%:~:.:h') + return vim_funcs['expand']('%:~:.:h') + os.sep def file_name(): @@ -126,13 +127,20 @@ def file_type(): return vim.eval('&filetype') or None -def line_percent(): +def line_percent(gradient=False): '''Return the cursor position in the file as a percentage. ''' line_current = vim_funcs['line']('.') line_last = vim_funcs['line']('$') + percentage = int(line_current * 100 // line_last) - return line_current * 100 // line_last + if not gradient: + return percentage + + return { + 'contents': percentage, + 'highlight': 'line_percent_gradient' + str(int(5 * percentage // 100) + 1), + } def line_current(): diff --git a/powerline/renderer.py b/powerline/renderer.py new file mode 100644 index 00000000..b60da225 --- /dev/null +++ b/powerline/renderer.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- + +from colorscheme import Colorscheme + + +class Renderer(object): + ATTR_BOLD = 1 + ATTR_ITALIC = 2 + ATTR_UNDERLINE = 4 + + def __init__(self, theme): + self.segments = [] + self.theme = theme + + def render(self, mode, width=None, contents_override=None): + '''Render all segments. + + When a width is provided, low-priority segments are dropped one at + a time until the line is shorter than the width, or only segments + with a negative priority are left. If one or more filler segments are + provided they will fill the remaining space until the desired width is + reached. + ''' + self.segments = self.theme.get_segments(mode, contents_override) + rendered_highlighted = self._render_segments(mode) + + if not width: + # No width specified, so we don't need to crop or pad anything + return rendered_highlighted + + # Create an ordered list of segments that can be dropped + segments_priority = [segment for segment in sorted(self.segments, key=lambda segment: segment['priority'], reverse=True) if segment['priority'] > 0] + + while self._total_len() > width and len(segments_priority): + self.segments.remove(segments_priority[0]) + segments_priority.pop(0) + + # Do another render pass so we can calculate the correct amount of filler space + self._render_segments(mode, False) + + # Distribute the remaining space on the filler segments + segments_fillers = [segment for segment in self.segments if segment['type'] == 'filler'] + if segments_fillers: + segments_fillers_len, segments_fillers_remainder = divmod((width - self._total_len()), len(segments_fillers)) + segments_fillers_contents = ' ' * segments_fillers_len + for segment in segments_fillers: + segment['contents'] = segments_fillers_contents + # Add remainder whitespace to the first filler segment + segments_fillers[0]['contents'] += ' ' * segments_fillers_remainder + + return self._render_segments(mode) + + def _render_segments(self, mode, render_highlighted=True): + '''Internal segment rendering method. + + This method loops through the segment array and compares the + foreground/background colors and divider properties and returns the + rendered statusline as a string. + + The method always renders the raw segment contents (i.e. without + highlighting strings added), and only renders the highlighted + statusline if render_highlighted is True. + ''' + rendered_highlighted = u'' + segments_len = len(self.segments) + mode = mode if mode in self.segments[0]['highlight'] else Colorscheme.default_mode_key + + for index, segment in enumerate(self.segments): + prev_segment = self.segments[index - 1] if index > 0 else None + next_segment = self.segments[index + 1] if index < segments_len - 1 else None + compare_segment = next_segment if segment['side'] == 'left' else prev_segment + + segment['rendered_raw'] = u'' + outer_padding = ' ' if index == 0 or index == segments_len - 1 else '' + divider_type = 'soft' if compare_segment['highlight'][mode]['bg'] == segment['highlight'][mode]['bg'] else 'hard' + divider = self.theme.get_divider(segment['side'], divider_type) + + divider_hl = '' + segment_hl = '' + + if render_highlighted: + if divider_type == 'hard': + divider_hl = self.hl(segment['highlight'][mode]['bg'], compare_segment['highlight'][mode]['bg'], False) + + segment_hl = self.hl(**segment['highlight'][mode]) + + if segment['type'] == 'filler': + rendered_highlighted += segment['contents'] or '' + elif segment['draw_divider'] or divider_type == 'hard': + # Draw divider if specified, or if it's a hard divider + # Note: Hard dividers are always drawn, regardless of + # the draw_divider option + if segment['side'] == 'left': + segment['rendered_raw'] += outer_padding + segment['contents'] + ' ' + divider + ' ' + rendered_highlighted += segment_hl + outer_padding + segment['contents'] + ' ' + divider_hl + divider + ' ' + else: + segment['rendered_raw'] += ' ' + divider + ' ' + segment['contents'] + outer_padding + rendered_highlighted += ' ' + divider_hl + divider + segment_hl + ' ' + segment['contents'] + outer_padding + elif segment['contents']: + # Segments without divider + if segment['side'] == 'left': + segment['rendered_raw'] += outer_padding + segment['contents'] + rendered_highlighted += segment_hl + outer_padding + segment['contents'] + else: + segment['rendered_raw'] += segment['contents'] + outer_padding + rendered_highlighted += segment_hl + segment['contents'] + outer_padding + else: + raise ValueError('Unknown segment type') + + return rendered_highlighted + + def _total_len(self): + '''Return total/rendered length of all segments. + + This method uses the rendered_raw property of the segments and requires + that the segments have been rendered using the render() method first. + ''' + return len(''.join([segment['rendered_raw'] for segment in self.segments])) + + def hl(self, fg=None, bg=None, attr=None): + raise NotImplementedError diff --git a/powerline/theme.py b/powerline/theme.py new file mode 100644 index 00000000..6a2c6bec --- /dev/null +++ b/powerline/theme.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- + +import importlib + + +class Theme(object): + def __init__(self, ext, colorscheme, theme_config, common_config): + self.colorscheme = colorscheme + self.dividers = theme_config.get('dividers', common_config['dividers']) + self.segments = [] + + for side in ['left', 'right']: + for segment in theme_config['segments'].get(side, []): + contents = None + contents_func = None + segment_type = segment.get('type', 'function') + segment_module = segment.get('module', 'core') + + if segment_type == 'function': + # Import segment function and assign it to the contents + function_module = 'powerline.ext.{0}.segments.{1}'.format(ext, segment_module) + function_name = segment['name'] + contents_func = getattr(importlib.import_module(function_module), function_name) + elif segment_type == 'string': + contents = segment.get('contents') + elif segment_type == 'filler': + pass + else: + raise TypeError('Unknown segment type: {0}'.format(segment_type)) + + highlighting_group = segment.get('highlight', segment.get('name')) + + self.segments.append({ + 'key': None if segment_type != 'function' else '{0}.{1}'.format(segment_module, function_name), + 'type': segment_type, + 'highlight': self.colorscheme.get_group_highlighting(highlighting_group), + 'before': segment.get('before', ''), + 'after': segment.get('after', ''), + 'contents_func': contents_func, + 'contents': contents, + 'args': segment.get('args', {}), + 'ljust': segment.get('ljust', False), + 'rjust': segment.get('rjust', False), + 'priority': segment.get('priority', -1), + 'draw_divider': segment.get('draw_divider', True), + 'side': side, + 'exclude_modes': segment.get('exclude_modes', []), + 'include_modes': segment.get('include_modes', []), + }) + + def get_divider(self, side='left', type='soft'): + '''Return segment divider. + ''' + return self.dividers[side][type] + + def get_segments(self, mode, contents_override=None): + '''Return all segments. + + Function segments are called, and all segments get their before/after + and ljust/rjust properties applied. + ''' + contents_override = contents_override or {} + return_segments = [] + for segment in self.segments: + if mode in segment['exclude_modes'] or (segment['include_modes'] and segment not in segment['include_modes']): + continue + + if segment['type'] == 'function': + contents = contents_override.get(segment['key'], segment['contents_func'](**segment['args'])) + + if contents is None: + continue + + try: + segment['highlight'] = self.colorscheme.get_group_highlighting(contents['highlight']) + segment['contents'] = contents['contents'] + except TypeError: + segment['contents'] = contents + elif segment['type'] == 'filler' or (segment['type'] == 'string' and segment['contents'] is not None): + pass + else: + continue + + if not segment['key'] in contents_override: + # Only apply before/after/just to non-overridden segments + segment['contents'] = unicode(segment['before'] + unicode(segment['contents']) + segment['after'])\ + .ljust(segment['ljust'])\ + .rjust(segment['rjust']) + + return_segments.append(segment) + + return return_segments diff --git a/powerline/themes/vim/default.json b/powerline/themes/vim/default.json new file mode 100644 index 00000000..0b2d1ac2 --- /dev/null +++ b/powerline/themes/vim/default.json @@ -0,0 +1,88 @@ +{ + "name": "default", + "segments": { + "left": [ + { + "name": "mode", + "exclude_modes": ["nc"] + }, + { + "name": "paste_indicator", + "exclude_modes": ["nc"], + "priority": 10 + }, + { + "name": "branch", + "exclude_modes": ["nc"], + "priority": 60, + "before": "⭠ " + }, + { + "name": "readonly_indicator", + "exclude_modes": ["nc"], + "draw_divider": false + }, + { + "name": "file_directory", + "priority": 40, + "draw_divider": false + }, + { + "name": "file_name", + "draw_divider": false + }, + { + "name": "modified_indicator", + "args": { "text": "+" }, + "exclude_modes": ["nc"], + "before": " " + }, + { + "type": "filler", + "highlight": ["background"] + } + ], + "right": [ + { + "name": "file_format", + "draw_divider": false, + "exclude_modes": ["nc"], + "priority": 50 + }, + { + "name": "file_encoding", + "exclude_modes": ["nc"], + "priority": 50 + }, + { + "name": "file_type", + "exclude_modes": ["nc"], + "priority": 50 + }, + { + "name": "line_percent", + "args": { "gradient": true }, + "priority": 30, + "after": "", + "rjust": 4 + }, + { + "type": "string", + "contents": "⭡ ", + "highlight": ["line_current_symbol", "line_current"] + }, + { + "name": "line_current", + "draw_divider": false, + "rjust": 3 + }, + { + "name": "col_current", + "draw_divider": false, + "priority": 30, + "before": ":", + "ljust": 3 + } + ] + } +} diff --git a/vim/segments/__init__.py b/vim/segments/__init__.py deleted file mode 100644 index cf7d729a..00000000 --- a/vim/segments/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# flake8: noqa - -from core import (mode, modified_indicator, paste_indicator, - readonly_indicator, branch, file_directory, file_name, file_format, - file_encoding, file_type, line_percent, line_current, col_current)