Skip to content

Commit 6afbe52

Browse files
committed
add theme support for fully configurable colours and do a bit of tidying up of
colour stuff in general (replace all those curses.color_pair(self._C['blah'}) calls with just "get_colpair('blah')"
1 parent aa867b0 commit 6afbe52

File tree

4 files changed

+141
-159
lines changed

4 files changed

+141
-159
lines changed

bpython/cli.py

Lines changed: 96 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import pydoc
4646
import types
4747
import unicodedata
48+
from itertools import chain
4849
from cStringIO import StringIO
4950
from locale import LC_ALL, getpreferredencoding, setlocale
5051
from optparse import OptionParser
@@ -133,54 +134,14 @@ def DEBUG(s):
133134
open('/tmp/bpython-debug', 'a').write("%s\n" % (str(s), ))
134135

135136

136-
def make_colours():
137-
"""Init all the colours in curses and bang them into a dictionary"""
137+
def get_color(name):
138+
return colors[OPTS.color_scheme[name]]
138139

139-
OPTS.light = {
140-
'listwin': 'b',
141-
'args': 'g',
142-
'kwvalue': 'b',
143-
'self': 'r',
144-
'*args': 'g',
145-
'**kwargs': 'g',
146-
'punctuation': 'k',
147-
'paren': 'y',
148-
'function': 'r',
149-
'more': 'g',
150-
'prompt': 'r',
151-
'output': 'k',
152-
'statusbar': 'r',
153-
'error': 'b',
154-
}
155-
OPTS.dark = {
156-
'listwin': 'c',
157-
'args': 'g',
158-
'kwvalue': 'b',
159-
'self': 'r',
160-
'*args': 'g',
161-
'**kwargs': 'g',
162-
'punctuation': 'm',
163-
'paren': 'y',
164-
'function': 'r',
165-
'more': 'y',
166-
'prompt': 'g',
167-
'output': 'w',
168-
'statusbar': 'c',
169-
'error': 'y',
170-
}
171-
if OPTS.color_scheme == 'light':
172-
bg = 7
173-
OPTS.extras = OPTS.light
174-
else:
175-
bg = 0
176-
OPTS.extras = OPTS.dark
140+
def get_colpair(name):
141+
return curses.color_pair(get_color(name) + 1)
177142

178-
for i in range(63):
179-
if i > 7:
180-
j = i / 8
181-
else:
182-
j = bg
183-
curses.init_pair(i+1, i % 8, j)
143+
def make_colors():
144+
"""Init all the colours in curses and bang them into a dictionary"""
184145

185146
# blacK, Red, Green, Yellow, Blue, Magenta, Cyan, White, Default:
186147
c = {
@@ -194,7 +155,13 @@ def make_colours():
194155
'w' : 7,
195156
'd' : -1,
196157
}
197-
c.update((k, c[v]) for k, v in OPTS.extras.iteritems())
158+
for i in range(63):
159+
if i > 7:
160+
j = i / 8
161+
else:
162+
j = c[OPTS.color_scheme['background']]
163+
curses.init_pair(i+1, i % 8, j)
164+
198165
return c
199166

200167

@@ -275,7 +242,7 @@ def showtraceback(self):
275242
def writetb(self, l):
276243
"""This outputs the traceback and should be overridden for anything
277244
fancy."""
278-
map(self.write, ["\x01%s\x03%s" % (OPTS.extras['error'], i) for i in l])
245+
map(self.write, ["\x01%s\x03%s" % (OPTS.color_scheme['error'], i) for i in l])
279246

280247

281248
class Repl(object):
@@ -714,9 +681,7 @@ def lsize():
714681

715682
for ix, i in enumerate(v_items):
716683
padding = (wl - len(i)) * ' '
717-
self.list_win.addstr(
718-
i + padding,
719-
curses.color_pair(self._C["listwin"]+1))
684+
self.list_win.addstr(i + padding, get_colpair('main'))
720685
if ((cols == 1 or (ix and not (ix+1) % cols))
721686
and ix + 1 < len(v_items)):
722687
self.list_win.addstr('\n ')
@@ -730,6 +695,7 @@ def lsize():
730695

731696
self.statusbar.win.touchwin()
732697
self.statusbar.win.noutrefresh()
698+
self.list_win.attron(get_colpair('main'))
733699
self.list_win.border()
734700
self.scr.touchwin()
735701
self.scr.cursyncup()
@@ -761,8 +727,8 @@ def mkargspec(self, topline, down):
761727

762728
self.list_win.addstr('\n ')
763729
self.list_win.addstr(fn,
764-
curses.color_pair(self._C["function"]+1) | curses.A_BOLD)
765-
self.list_win.addstr(': (', curses.color_pair(self._C["paren"]+1))
730+
get_colpair('name') | curses.A_BOLD)
731+
self.list_win.addstr(': (', get_colpair('name'))
766732
maxh = self.scr.getmaxyx()[0]
767733

768734
for k, i in enumerate(args):
@@ -788,33 +754,27 @@ def mkargspec(self, topline, down):
788754
self.list_win.addstr('\n\t')
789755

790756
if str(i) == 'self' and k == 0:
791-
color = self._C["self"]
757+
color = get_colpair('name')
792758
else:
793-
color = self._C["args"]
759+
color = get_colpair('token')
794760

795-
self.list_win.addstr(str(i),
796-
curses.color_pair(color + 1) | curses.A_BOLD)
761+
self.list_win.addstr(str(i), color | curses.A_BOLD)
797762
if kw:
798-
self.list_win.addstr('=',
799-
curses.color_pair(self._C["punctuation"]+1))
800-
self.list_win.addstr(kw, curses.color_pair(self._C["kwvalue"]+1))
763+
self.list_win.addstr('=', get_colpair('punctuation'))
764+
self.list_win.addstr(kw, get_colpair('token'))
801765
if k != len(args) -1:
802-
self.list_win.addstr(', ',
803-
curses.color_pair(self._C["punctuation"]+1))
766+
self.list_win.addstr(', ', get_colpair("punctuation"))
804767

805768
if _args:
806769
if args:
807-
self.list_win.addstr(', ',
808-
curses.color_pair(self._C["args"]+1))
809-
self.list_win.addstr('*%s' % (_args, ),
810-
curses.color_pair(self._C["*args"]+1))
770+
self.list_win.addstr(', ', get_colpair('punctuation'))
771+
self.list_win.addstr('*%s' % (_args, ), get_colpair('token'))
772+
811773
if _kwargs:
812774
if args or _args:
813-
self.list_win.addstr(', ',
814-
curses.color_pair(self._C["punctuation"]+1))
815-
self.list_win.addstr('**%s' % (_kwargs, ),
816-
curses.color_pair(self._C["**kwargs"]+1))
817-
self.list_win.addstr(')', curses.color_pair(self._C["paren"]+1))
775+
self.list_win.addstr(', ', get_colpair('punctuation'))
776+
self.list_win.addstr('**%s' % (_kwargs, ), get_colpair('token'))
777+
self.list_win.addstr(')', get_colpair('punctuation'))
818778

819779
return r
820780

@@ -1008,13 +968,14 @@ def reevaluate(self):
1008968
def prompt(self, more):
1009969
"""Show the appropriate Python prompt"""
1010970
if not more:
1011-
self.echo("\x01%s\x03>>> " % (OPTS.extras['prompt'],))
971+
self.echo("\x01%s\x03>>> " % (OPTS.color_scheme['prompt'],))
1012972
self.stdout_hist += '>>> '
1013-
self.s_hist.append('\x01%s\x03>>> \x04' % (OPTS.extras['prompt'],))
973+
self.s_hist.append('\x01%s\x03>>> \x04' % (OPTS.color_scheme['prompt'],))
1014974
else:
1015-
self.echo("\x01%s\x03... " % (OPTS.extras['more'],))
975+
self.echo("\x01%s\x03... " % (OPTS.color_scheme['prompt_more'],))
1016976
self.stdout_hist += '... '
1017-
self.s_hist.append('\x01%s\x03... \x04' % (OPTS.extras['more'],))
977+
self.s_hist.append('\x01%s\x03... \x04' %
978+
(OPTS.color_scheme['prompt_more'],))
1018979

1019980
def repl(self):
1020981
"""Initialise the repl and jump into the loop. This method also has to
@@ -1136,7 +1097,7 @@ def echo(self, s, redraw=True):
11361097
if isinstance(s, unicode):
11371098
s = s.encode(getpreferredencoding())
11381099

1139-
a = curses.color_pair(colors[OPTS.extras['output']]+1)
1100+
a = get_colpair('output')
11401101
if '\x01' in s:
11411102
rx = re.search('\x01([a-z])([a-z]?)', s)
11421103
if rx:
@@ -1807,7 +1768,7 @@ def init_wins(scr, cols):
18071768
#
18081769
statusbar = Statusbar(scr, main_win,
18091770
".:: <C-d> Exit <C-r> Rewind <F2> Save <F8> Pastebin ::.",
1810-
cols[OPTS.extras['statusbar']] + 1)
1771+
cols[OPTS.color_scheme['main']] + 1)
18111772

18121773
return main_win, statusbar
18131774

@@ -1915,30 +1876,32 @@ def migrate_rc(path):
19151876
config.write(f)
19161877
f.close()
19171878
os.rename(path, os.path.expanduser('~/.bpythonrc.bak'))
1918-
print "The configuration file for bpython has been changed. A new .bpython.ini file has been created in your home directory."
1919-
print "The existing .bpythonrc file has been renamed to .bpythonrc.bak and it can be removed."
1879+
print ("The configuration file for bpython has been changed. A new "
1880+
".bpython.ini file has been created in your home directory.")
1881+
print ("The existing .bpythonrc file has been renamed to .bpythonrc.bak "
1882+
"and it can be removed.")
19201883
print "Press enter to continue."
19211884
raw_input()
19221885

19231886

1887+
class CP(ConfigParser):
1888+
def safeget(self, section, option, default):
1889+
"""safet get method using default values"""
1890+
try:
1891+
v = self.get(section, option)
1892+
except NoSectionError:
1893+
v = default
1894+
except NoOptionError:
1895+
v = default
1896+
if isinstance(v, bool):
1897+
return v
1898+
try:
1899+
return int(v)
1900+
except ValueError:
1901+
return v
19241902

19251903
def loadini(configfile):
19261904
"""Loads .ini configuration file and stores its values in OPTS"""
1927-
class CP(ConfigParser):
1928-
def safeget(self, section, option, default):
1929-
"""safet get method using default values"""
1930-
try:
1931-
v = self.get(section, option)
1932-
except NoSectionError:
1933-
v = default
1934-
except NoOptionError:
1935-
v = default
1936-
if isinstance(v, bool):
1937-
return v
1938-
try:
1939-
return int(v)
1940-
except ValueError:
1941-
return v
19421905

19431906
configfile = os.path.expanduser(configfile)
19441907

@@ -1952,7 +1915,43 @@ def safeget(self, section, option, default):
19521915
OPTS.hist_file = config.safeget('general', 'hist_file', '~/.pythonhist')
19531916
OPTS.hist_length = config.safeget('general', 'hist_length', 100)
19541917
OPTS.flush_output = config.safeget('general', 'flush_output', True)
1955-
OPTS.color_scheme = config.safeget('general', 'color_scheme', 'dark')
1918+
color_scheme_name = config.safeget('general', 'color_scheme', 'default')
1919+
1920+
if color_scheme_name == 'default':
1921+
OPTS.color_scheme = {
1922+
'keyword': 'Y',
1923+
'name': 'B',
1924+
'comment': 'b',
1925+
'string': 'g',
1926+
'error': 'r',
1927+
'number': 'g',
1928+
'operator': 'c',
1929+
'punctuation': 'y',
1930+
'token': 'g',
1931+
'background': 'k',
1932+
'output': 'w',
1933+
'main': 'c',
1934+
'prompt': 'r',
1935+
'prompt_more': 'g',
1936+
}
1937+
else:
1938+
path = os.path.expanduser('~/.bpython/%s.theme' % (color_scheme_name,))
1939+
# XXX ConfigParser doesn't raise an IOError if it tries to read a file
1940+
# that doesn't exist which isn't helpful to us:
1941+
if not os.path.isfile(path):
1942+
raise IOError("'%s' is not a readable file" % (path,))
1943+
load_theme(color_scheme_name)
1944+
1945+
def load_theme(name):
1946+
path = os.path.expanduser('~/.bpython/%s.theme' % (name,))
1947+
theme = CP()
1948+
theme.read(path)
1949+
OPTS.color_scheme = {}
1950+
for k, v in chain(theme.items('syntax'), theme.items('interface')):
1951+
if theme.has_option('syntax', k):
1952+
OPTS.color_scheme[k] = theme.get('syntax', k)
1953+
else:
1954+
OPTS.color_scheme[k] = theme.get('interface', k)
19561955

19571956

19581957
class FakeDict(object):
@@ -1968,11 +1967,7 @@ def newwin(*args):
19681967
"""Wrapper for curses.newwin to automatically set background colour on any
19691968
newly created window."""
19701969
win = curses.newwin(*args)
1971-
if OPTS.color_scheme == 'light':
1972-
bg = colors['w']
1973-
else:
1974-
bg = colors['k']
1975-
colpair = curses.color_pair(bg)
1970+
colpair = get_colpair('background')
19761971
win.bkgd(' ', colpair)
19771972
return win
19781973

@@ -1998,7 +1993,7 @@ def main_curses(scr):
19981993
try:
19991994
curses.start_color()
20001995
curses.use_default_colors()
2001-
cols = make_colours()
1996+
cols = make_colors()
20021997
except curses.error:
20031998
cols = FakeDict(-1)
20041999

0 commit comments

Comments
 (0)