Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] feature/status_components #233

Open
wants to merge 37 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
f2ce9b3
feature/color_manager: add color manager
Consolatis Jan 9, 2018
1465888
feature/color_manager: use color manager
Consolatis Jan 9, 2018
1e2968f
viewer.py: for linelight return default editor fg in case nothing mat…
Consolatis Jan 9, 2018
588bc28
color_pairs.py: invalid colors: fall back to COLOR_WHITE if color cou…
Consolatis Jan 9, 2018
cc04471
ui.py: warn user about missing transparency and default colors
Consolatis Jan 9, 2018
0cd359f
fixup
Consolatis Jan 9, 2018
0f6efc8
color_pairs.py: change logging level from info to debug
Consolatis Jan 9, 2018
e8c798f
feature/color_manager: add legend color pair
Consolatis Jan 9, 2018
3237959
ui.py: remove limited_colors as colors are now dynamic
Consolatis Jan 9, 2018
f899c94
feature/colormanager: move setup to color_pairs.py, add config binding
Consolatis Jan 25, 2018
41fd0b7
feature/colormanager: rename color_pairs to color_manager
Consolatis Jan 25, 2018
77848db
[WIP] fix all fixmes and todos
Consolatis Mar 8, 2018
6d1bf5e
[WIP] rename Python file from color_manager.py to color_manager_curse…
Consolatis Mar 8, 2018
a57608c
[WIP] codestyle fixup
Consolatis Mar 8, 2018
4fb2bd3
feature/status_components: add statusbar.py
Consolatis Jan 26, 2018
aaad67a
feature/status_components: add basic components
Consolatis Jan 26, 2018
b366fea
feature/status_components: add filelist component
Consolatis Jan 26, 2018
301e09b
feature/status_components: add linter components
Consolatis Jan 26, 2018
4644d96
feature/status_components: use statusbar components
Consolatis Jan 26, 2018
63dc701
[WIP] disable excessive debug logging
Consolatis Jan 26, 2018
3cc1499
[WIP] restore correct resizing behaviour
Consolatis Jan 26, 2018
ae65ff8
[WIP] restore python2 compability (no list.clear(), no properties for…
Consolatis Jan 27, 2018
80f87da
[WIP] add StatusComponent priorities, add docstrings for StatusCompon…
Consolatis Feb 5, 2018
d46c9eb
[WIP] use StatusComponent priorities, more docstrings
Consolatis Feb 6, 2018
d12413d
[WIP] fixup
Consolatis Feb 6, 2018
2096d6e
[WIP] fixup
Consolatis Feb 7, 2018
1daefe7
[WIP] fixup
Consolatis Feb 7, 2018
116e65c
[WIP] fix statusbar invalidation on config reload
Consolatis Feb 7, 2018
5c16ba1
[WIP] invalidate state on prompt exit
Consolatis Feb 9, 2018
ca3abd4
[WIP] add limit config option for filelist module
Consolatis Feb 10, 2018
4546a1e
[WIP] fixup
Consolatis Feb 10, 2018
7a2e065
[WIP] improve limit config option for filelist module, change default…
Consolatis Feb 10, 2018
f950ece
[WIP] modules/filelist.py: document default options, replace center a…
Consolatis Feb 11, 2018
e0320f8
[WIP] invalidate state on prompt exit: verify bottom bar is actually …
Consolatis Feb 11, 2018
dd3a72e
[WIP] reduce logging
Consolatis Mar 1, 2018
ef575fb
[WIP] status.py: add new formatted component 'cursors', use it as def…
Consolatis Mar 1, 2018
03cb939
[WIP] add battery indicator to default top bar
Consolatis Mar 1, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
251 changes: 251 additions & 0 deletions suplemon/color_manager_curses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
# -*- encoding: utf-8
"""
Manage curses color pairs
"""

import curses
import logging
from traceback import format_stack


class ColorManager:
def __init__(self, app):
self._app = app
self.logger = logging.getLogger(__name__ + "." + ColorManager.__name__)
self._colors = dict()

# color_pair(0) is hardcoded
# https://docs.python.org/3/library/curses.html#curses.init_pair
self._color_count = 1
self._invalid_fg = curses.COLOR_WHITE
self._invalid_bg = curses.COLOR_BLACK if curses.COLORS < 8 else curses.COLOR_RED

# dynamic in case terminal does not support use_default_colors()
self._default_fg = -1
self._default_bg = -1
self._setup_colors()
self._load_color_theme()
self._app.set_event_binding("config_loaded", "after", self._load_color_theme)

def _setup_colors(self):
"""Initialize color support and define colors."""
curses.start_color()

self.termname = curses.termname().decode('utf-8')
self.logger.info(
"Currently running with TERM '%s' which provides %i colors and %i color pairs according to ncurses." %
(self.termname, curses.COLORS, curses.COLOR_PAIRS)
)

if curses.COLORS == 8:
self.logger.info("Enhanced colors not supported.")
self.logger.info(
"Depending on your terminal emulator 'export TERM=%s-256color' may help." %
self.termname
)
self._app.config["editor"]["theme"] = "8colors"

try:
curses.use_default_colors()
except:
self.logger.warning(
"Failed to load curses default colors. " +
"You will have no transparency or terminal defined default colors."
)
# https://docs.python.org/3/library/curses.html#curses.init_pair
# "[..] the 0 color pair is wired to white on black and cannot be changed"
self._set_default_fg(curses.COLOR_WHITE)
self._set_default_bg(curses.COLOR_BLACK)

def _load_color_theme(self, *args):
colors = self._get_config_colors()
for key in colors:
values = colors[key]
self.add_translate(
key,
values.get('fg', None),
values.get('bg', None),
values.get('attribs', None)
)
self._app.themes.use(self._app.config["editor"]["theme"])

def _get_config_colors(self):
if curses.COLORS == 8:
return self._app.config["display"]["colors_8"]
elif curses.COLORS == 88:
return self._app.config["display"]["colors_88"]
elif curses.COLORS == 256:
return self._app.config["display"]["colors_256"]
else:
self.logger.warning(
"No idea how to handle a color count of %i. Defaulting to 8 colors." % curses.COLORS
)
return self._app.config["display"]["colors_8"]

def _set_default_fg(self, color):
self._default_fg = color

def _set_default_bg(self, color):
self._default_bg = color

def _get(self, name, index=None, default=None, log_missing=True):
ret = self._colors.get(str(name), None)
if ret is None:
if log_missing:
self.logger.warning("Color '%s' not initialized. Maybe some issue with your theme?" % name)
return default
if index is not None:
return ret[index]
return ret

def get(self, name):
""" Return colorpair ORed attribs or a fallback """
return self._get(name, index=1, default=curses.color_pair(0))

def get_alt(self, name, alt):
""" Return colorpair ORed attribs or alt """
return self._get(name, index=1, default=alt, log_missing=False)

def get_fg(self, name):
""" Return foreground color as integer or hardcoded invalid_fg (white) as fallback """
return self._get(name, index=2, default=self._invalid_fg)

def get_bg(self, name):
""" Return background color as integer or hardcoded invalid_bg (red) as fallback"""
return self._get(name, index=3, default=self._invalid_bg)

def get_color(self, name):
""" Alternative for get(name) """
return self.get(name)

def get_all(self, name):
""" color, fg, bg, attrs = get_all("something") """
ret = self._get(name)
if ret is None:
return (None, None, None, None)
return ret[1:]

def __contains__(self, name):
""" Check if a color pair with this name exists """
return str(name) in self._colors

def add_translate(self, name, fg, bg, attributes=None):
"""
Store or update color definition.
fg and bg can be of form "blue" or "color162".
attributes can be a list of attribute names like ["bold", "underline"].
"""
return self.add_curses(
name,
self._translate_color(fg, usage_hint="fg"),
self._translate_color(bg, usage_hint="bg"),
self._translate_attributes(attributes)
)

def add_curses(self, name, fg, bg, attrs=0):
"""
Store or update color definition.
fg, bg and attrs must be valid curses values.
"""
name = str(name)
if name in self._colors:
# Redefine existing color pair
index, color, _fg, _bg, _attrs = self._colors[name]
self.logger.debug(
"Updating exiting curses color pair with index %i, name '%s', fg=%s, bg=%s and attrs=%s" % (
index, name, fg, bg, attrs
)
)
else:
# Create new color pair
index = self._color_count
self.logger.debug(
"Creating new curses color pair with index %i, name '%s', fg=%s, bg=%s and attrs=%s" % (
index, name, fg, bg, attrs
)
)
if index < curses.COLOR_PAIRS:
self._color_count += 1
else:
self.logger.warning(
"Failed to create new color pair for "
"'%s', the terminal description for '%s' only supports up to %i color pairs." %
(name, self.termname, curses.COLOR_PAIRS)
)
try:
color = curses.color_pair(0) | attrs
except:
self.logger.warning("Invalid attributes: '%s'" % str(attrs))
color = curses.color_pair(0)
self._colors[name] = (0, color, curses.COLOR_WHITE, curses.COLOR_BLACK, attrs)
return color
try:
curses.init_pair(index, fg, bg)
color = curses.color_pair(index) | attrs
except Exception as e:
self.logger.warning(
"Failed to create or update curses color pair with "
"index %i, name '%s', fg=%s, bg=%s, attrs=%s. error was: %s" %
(index, name, fg, bg, str(attrs), e)
)
color = curses.color_pair(0)

self._colors[name] = (index, color, fg, bg, attrs)
return color

def _translate_attributes(self, attributes):
""" Translate list of attributes into native curses format """
if attributes is None:
return 0
val = 0
for attrib in attributes:
val |= getattr(curses, "A_" + attrib.upper(), 0)
return val

def _translate_color(self, color, usage_hint=None):
"""
Translate color name of form 'blue' or 'color252' into native curses format.
On error return hardcoded invalid_fg or _bg (white or red) color.
"""
if color is None:
return self._invalid_fg if usage_hint == "fg" else self._invalid_bg

color_i = getattr(curses, "COLOR_" + color.upper(), None)
if color_i is not None:
return color_i

color = color.lower()
if color == "default":
if usage_hint == "fg":
return self._default_fg
elif usage_hint == "bg":
return self._default_bg
else:
self.logger.warning("Default color requested without usage_hint being one of fg, bg.")
self.logger.warning("This is likely a bug, please report at https://github.com/richrd/suplemon/issues")
self.logger.warning("and include the following stacktrace.")
for line in format_stack()[:-1]:
self.logger.warning(line.strip())
return self._invalid_bg
elif color.startswith("color"):
color_i = color[len("color"):]
elif color.startswith("colour"):
color_i = color[len("colour"):]
else:
self.logger.warning("Invalid color specified: '%s'" % color)
return self._invalid_fg if usage_hint == "fg" else self._invalid_bg

try:
color_i = int(color_i)
except:
self.logger.warning("Invalid color specified: '%s'" % color)
return self._invalid_fg if usage_hint == "fg" else self._invalid_bg

if color_i >= curses.COLORS:
self.logger.warning(
"The terminal description for '%s' does not support more than %i colors. Specified color was %s" %
(self.termname, curses.COLORS, color)
)
return self._invalid_fg if usage_hint == "fg" else self._invalid_bg

return color_i
69 changes: 59 additions & 10 deletions suplemon/config/defaults.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@
"\uFEFF": "\u2420"
},
// Whether to visually show white space chars
"show_white_space": true,
"show_white_space": false,
// Whether to ignore theme whitespace color
"ignore_theme_whitespace": false,
// Show tab indicators in whitespace
"show_tab_indicators": true,
// Tab indicator charatrer
Expand All @@ -126,20 +128,67 @@
},
// UI Display Settings
"display": {
// Show top status bar
"show_top_bar": true,
// Show app name and version in top bar
"show_app_name": true,
// Show list of open files in top bar
"show_file_list": true,
"status_top": {
"components": "editor_logo editor_name editor_version fill filelist fill battery clock",
"fillchar": " ",
"spacechar": " ",
"truncate": "left",
"default_align": "left"
},
"show_bottom_bar": true,
"status_bottom": {
"components": "app_status fill document_position cursors lint",
"fillchar": " ",
"spacechar": " ",
"truncate": "right",
"default_align": "left"
},
// Show indicator in the file list for files that are modified
// NOTE: if you experience performance issues, set this to false
"show_file_modified_indicator": true,
// Show the keyboard legend
"show_legend": true,
// Show the bottom status bar
"show_bottom_bar": true,
// Invert status bar colors (switch text and background colors)
"invert_status_bars": false
// Theme for 8 colors
"colors_8": {
// Another variant for linenumbers (and maybe status_*) is black, black, bold
"status_top": { "fg": "white", "bg": "black" },
"status_bottom": { "fg": "white", "bg": "black" },
"legend": { "fg": "white", "bg": "black" },
"linenumbers": { "fg": "white", "bg": "black" },
"linenumbers_lint_error": { "fg": "red", "bg": "black" },
"editor": { "fg": "default", "bg": "default" },
"editor_whitespace": { "fg": "black", "bg": "default", "attribs": [ "bold" ] },
"filelist_active": { "fg": "white", "bg": "black" },
"filelist_other": { "fg": "black", "bg": "black", "attribs": [ "bold" ] },
"lintlogo_warn": { "fg": "red", "bg": "black" }
},
// Theme for 88 colors
"colors_88": {
// Copy of colors_8; this needs an own default theme
"status_top": { "fg": "white", "bg": "black" },
"status_bottom": { "fg": "white", "bg": "black" },
"legend": { "fg": "white", "bg": "black" },
"linenumbers": { "fg": "white", "bg": "black" },
"linenumbers_lint_error": { "fg": "red", "bg": "black" },
"editor": { "fg": "default", "bg": "default" },
"editor_whitespace": { "fg": "black", "bg": "default", "attribs": [ "bold" ] },
"filelist_active": { "fg": "white", "bg": "black" },
"filelist_other": { "fg": "black", "bg": "black", "attribs": [ "bold" ] },
"lintlogo_warn": { "fg": "red", "bg": "black" }
},
// Theme for 256 colors
"colors_256": {
"status_top": { "fg": "color250", "bg": "black" },
"status_bottom": { "fg": "color250", "bg": "black" },
"legend": { "fg": "color250", "bg": "black" },
"linenumbers": { "fg": "color240", "bg": "black" },
"linenumbers_lint_error": { "fg": "color204", "bg": "black" },
"editor": { "fg": "default", "bg": "default" },
"editor_whitespace": { "fg": "color240", "bg": "default" },
"filelist_active": { "fg": "white", "bg": "black" },
"filelist_other": { "fg": "color240", "bg": "black" },
"lintlogo_warn": { "fg": "color204", "bg": "black" }
}
}
}
10 changes: 5 additions & 5 deletions suplemon/line.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def __init__(self, data=""):
data = data.data
self.data = data
self.x_scroll = 0
self.number_color = 8
self.state = None

def __getitem__(self, i):
return self.data[i]
Expand Down Expand Up @@ -38,14 +38,14 @@ def set_data(self, data):
data = data.get_data()
self.data = data

def set_number_color(self, color):
self.number_color = color
def set_state(self, state):
self.state = state

def find(self, what, start=0):
return self.data.find(what, start)

def strip(self, *args):
return self.data.strip(*args)

def reset_number_color(self):
self.number_color = 8
def reset_state(self):
self.state = None
Loading