Add saved settings, themes, etc.
This commit is contained in:
parent
dd6daa3d56
commit
548d292150
@ -1,3 +1,4 @@
|
||||
include debug_me.py
|
||||
include ez_setup.py
|
||||
include try-the-debugger.sh
|
||||
include example-theme.py
|
||||
|
3
example-theme.py
Normal file
3
example-theme.py
Normal file
@ -0,0 +1,3 @@
|
||||
palette.update({
|
||||
"source": (add_setting("black", "underline"), "dark green"),
|
||||
})
|
@ -4,6 +4,13 @@ VERSION = ".".join(str(nv) for nv in NUM_VERSION)
|
||||
|
||||
|
||||
|
||||
from pudb.settings import load_config, save_config
|
||||
CONFIG = load_config()
|
||||
save_config(CONFIG)
|
||||
|
||||
|
||||
|
||||
|
||||
CURRENT_DEBUGGER = []
|
||||
def _get_debugger():
|
||||
if not CURRENT_DEBUGGER:
|
||||
|
107
pudb/debugger.py
107
pudb/debugger.py
@ -13,6 +13,8 @@ Welcome to PuDB, the Python Urwid debugger.
|
||||
-------------------------------------------
|
||||
|
||||
Keys:
|
||||
Ctrl-p - edit preferences
|
||||
|
||||
n - step over ("next")
|
||||
s - step into
|
||||
c - continue
|
||||
@ -135,7 +137,7 @@ class Debugger(bdb.Bdb):
|
||||
self.ui.set_current_line(lineno, self.curframe.f_code.co_filename)
|
||||
self.ui.update_var_view()
|
||||
self.ui.update_stack()
|
||||
|
||||
|
||||
def move_up_frame(self):
|
||||
if self.curindex > 0:
|
||||
self.set_frame_index(self.curindex-1)
|
||||
@ -346,6 +348,9 @@ class DebuggerUI(FrameVarInfoKeeper):
|
||||
def edit_inspector_detail(w, size, key):
|
||||
var, pos = self.var_list._w.get_focus()
|
||||
|
||||
if var is None:
|
||||
return
|
||||
|
||||
fvi = self.get_frame_var_info(read_only=False)
|
||||
iinfo = fvi.get_inspect_info(var.id_path, read_only=False)
|
||||
|
||||
@ -465,7 +470,11 @@ class DebuggerUI(FrameVarInfoKeeper):
|
||||
|
||||
# stack listeners -----------------------------------------------------
|
||||
def examine_breakpoint(w, size, key):
|
||||
_, pos = self.bp_list._w.get_focus()
|
||||
bp_entry, pos = self.bp_list._w.get_focus()
|
||||
|
||||
if bp_entry is None:
|
||||
return
|
||||
|
||||
bp = self._get_bp_list()[pos]
|
||||
|
||||
if bp.cond is None:
|
||||
@ -870,50 +879,6 @@ class DebuggerUI(FrameVarInfoKeeper):
|
||||
self.message("No exception available.")
|
||||
|
||||
def run_shell(w, size, key):
|
||||
import pudb.shell as shell
|
||||
|
||||
if shell.HAVE_IPYTHON and shell.USE_IPYTHON == "ask":
|
||||
def ipython(w, size, key):
|
||||
self.quit_event_loop = ["ipython"]
|
||||
def classic(w, size, key):
|
||||
self.quit_event_loop = ["classic"]
|
||||
def cancel(w, size, key):
|
||||
self.quit_event_loop = [False]
|
||||
|
||||
result = dbg.ui.dialog(
|
||||
urwid.ListBox([urwid.Text(
|
||||
"You've asked to enter a Python shell.\n\n"
|
||||
"You appear to have IPython installed. If you wish to use it, "
|
||||
"you may hit 'i' or select the corresponding button on the "
|
||||
"right. If you prefer the 'classic' Python shell, hit '!' again or "
|
||||
"select that button instead.\n\n"
|
||||
"Sorry for bothering you, I won't ask again in this session."
|
||||
)]),
|
||||
[
|
||||
("Classic", "classic"),
|
||||
("IPython", "ipython"),
|
||||
("Cancel", False),
|
||||
],
|
||||
focus_buttons=True,
|
||||
bind_enter_esc=False,
|
||||
title="Shell Requested",
|
||||
extra_bindings=[
|
||||
("!", classic),
|
||||
("i", ipython),
|
||||
("esc", cancel),
|
||||
])
|
||||
|
||||
if result == False:
|
||||
return
|
||||
elif result == "ipython":
|
||||
shell.USE_IPYTHON = use_ipython = True
|
||||
elif result == "classic":
|
||||
shell.USE_IPYTHON = use_ipython = False
|
||||
|
||||
elif shell.HAVE_IPYTHON and shell.USE_IPYTHON:
|
||||
use_ipython = True
|
||||
else:
|
||||
use_ipython = False
|
||||
|
||||
self.screen.stop()
|
||||
|
||||
@ -925,12 +890,14 @@ class DebuggerUI(FrameVarInfoKeeper):
|
||||
|
||||
curframe = self.debugger.curframe
|
||||
|
||||
if use_ipython:
|
||||
from pudb import CONFIG
|
||||
import pudb.shell as shell
|
||||
if shell.HAVE_IPYTHON and CONFIG["shell"] == "ipython":
|
||||
runner = shell.run_ipython_shell
|
||||
else:
|
||||
runner = shell.run_classic_shell
|
||||
|
||||
runner(curframe.f_locals, curframe.f_globals,
|
||||
runner(curframe.f_locals, curframe.f_globals,
|
||||
first_shell_run)
|
||||
|
||||
self.screen.start()
|
||||
@ -965,9 +932,20 @@ class DebuggerUI(FrameVarInfoKeeper):
|
||||
self.debugger.set_quit()
|
||||
end()
|
||||
|
||||
def do_edit_config(w, size, key):
|
||||
from pudb.settings import edit_config, save_config
|
||||
from pudb import CONFIG
|
||||
edit_config(self, CONFIG)
|
||||
save_config(CONFIG)
|
||||
self.setup_palette(self.screen)
|
||||
|
||||
for sl in self.source:
|
||||
sl._invalidate()
|
||||
|
||||
def help(w, size, key):
|
||||
self.message(HELP_TEXT, title="PuDB Help")
|
||||
|
||||
|
||||
self.top.listen("o", show_output)
|
||||
self.top.listen("!", run_shell)
|
||||
self.top.listen("e", show_traceback)
|
||||
@ -979,6 +957,7 @@ class DebuggerUI(FrameVarInfoKeeper):
|
||||
self.top.listen("B", RHColumnFocuser(2))
|
||||
|
||||
self.top.listen("q", quit)
|
||||
self.top.listen("ctrl p", do_edit_config)
|
||||
self.top.listen("H", help)
|
||||
self.top.listen("f1", help)
|
||||
self.top.listen("?", help)
|
||||
@ -1068,8 +1047,10 @@ class DebuggerUI(FrameVarInfoKeeper):
|
||||
may_use_fancy_formats = isinstance(screen, RawScreen) and \
|
||||
not hasattr(urwid.escape, "_fg_attr_xterm")
|
||||
|
||||
from pudb import CONFIG
|
||||
from pudb.theme import get_palette
|
||||
screen.register_palette(get_palette(may_use_fancy_formats))
|
||||
screen.register_palette(
|
||||
get_palette(may_use_fancy_formats, CONFIG["theme"]))
|
||||
|
||||
# UI enter/exit -----------------------------------------------------------
|
||||
def show(self):
|
||||
@ -1101,6 +1082,30 @@ class DebuggerUI(FrameVarInfoKeeper):
|
||||
self.message("Package 'pygments' not found. "
|
||||
"Syntax highlighting disabled.")
|
||||
|
||||
from pudb import CONFIG
|
||||
WELCOME_LEVEL = "b"
|
||||
if CONFIG["seen_welcome"] < WELCOME_LEVEL:
|
||||
CONFIG["seen_welcome"] = WELCOME_LEVEL
|
||||
from pudb import VERSION
|
||||
self.message("Welcome to PudB %s!\n\n"
|
||||
"PuDB is a full-screen, console-based visual debugger for Python. "
|
||||
" Its goal is to provide all the niceties of modern GUI-based "
|
||||
"debuggers in a more lightweight and keyboard-friendly package. "
|
||||
"PuDB allows you to debug code right where you write and test it--in "
|
||||
"a terminal. If you've worked with the excellent (but nowadays "
|
||||
"ancient) DOS-based Turbo Pascal or C tools, PuDB's UI might "
|
||||
"look familiar.\n\n"
|
||||
"New features in version 0.93:\n\n"
|
||||
"- Stored preferences (no more pesky IPython prompt!)\n"
|
||||
"- Themes\n"
|
||||
"- Line numbers (optional)\n"
|
||||
"\nHit Ctrl-P to set up PuDB.\n\n"
|
||||
"If you're new here, welcome! The help screen (invoked by hitting "
|
||||
"'?' after this message) should get you on your way." % VERSION)
|
||||
|
||||
from pudb.settings import save_config
|
||||
save_config(CONFIG)
|
||||
|
||||
try:
|
||||
if toplevel is None:
|
||||
toplevel = self.top
|
||||
@ -1130,8 +1135,8 @@ class DebuggerUI(FrameVarInfoKeeper):
|
||||
|
||||
from pudb import VERSION
|
||||
caption = [(None,
|
||||
u"PuDB %s - The Python Urwid debugger - Hit ? for help"
|
||||
u" - (C) Andreas Kloeckner 2009"
|
||||
u"PuDB %s - ?:help, n:next, s:step into, b:breakpoint, o:console,"
|
||||
"t:run to cursor, !:python shell"
|
||||
% VERSION)]
|
||||
|
||||
if self.debugger.post_mortem:
|
||||
|
145
pudb/settings.py
Normal file
145
pudb/settings.py
Normal file
@ -0,0 +1,145 @@
|
||||
import os
|
||||
from ConfigParser import ConfigParser
|
||||
|
||||
# minor LGPL violation: stolen from python-xdg
|
||||
|
||||
_home = os.environ.get('HOME', '/')
|
||||
xdg_data_home = os.environ.get('XDG_DATA_HOME',
|
||||
os.path.join(_home, '.local', 'share'))
|
||||
|
||||
xdg_config_home = os.environ.get('XDG_CONFIG_HOME',
|
||||
os.path.join(_home, '.config'))
|
||||
|
||||
xdg_config_dirs = [xdg_config_home] + \
|
||||
os.environ.get('XDG_CONFIG_DIRS', '/etc/xdg').split(':')
|
||||
|
||||
def save_config_path(*resource):
|
||||
resource = os.path.join(*resource)
|
||||
assert not resource.startswith('/')
|
||||
path = os.path.join(xdg_config_home, resource)
|
||||
if not os.path.isdir(path):
|
||||
os.makedirs(path, 0700)
|
||||
return path
|
||||
|
||||
# end LGPL violation
|
||||
|
||||
CONF_SECTION = "pudb"
|
||||
|
||||
def load_config():
|
||||
from os.path import join, isdir
|
||||
|
||||
cparser = ConfigParser()
|
||||
|
||||
conf_dict = {}
|
||||
try:
|
||||
cparser.read([
|
||||
join(cdir, 'pudb', 'pudb.cfg')
|
||||
for cdir in xdg_config_dirs if isdir(cdir)])
|
||||
|
||||
if cparser.has_section(CONF_SECTION):
|
||||
conf_dict.update(dict(cparser.items(CONF_SECTION)))
|
||||
except:
|
||||
pass
|
||||
|
||||
conf_dict.setdefault("shell", "classic")
|
||||
conf_dict.setdefault("theme", "classic")
|
||||
conf_dict.setdefault("line_numbers", False)
|
||||
conf_dict.setdefault("seen_welcome", "a")
|
||||
|
||||
def hack_bool(name):
|
||||
try:
|
||||
if conf_dict[name].lower() in ["0", "false", "off"]:
|
||||
conf_dict[name] = False
|
||||
except:
|
||||
pass
|
||||
|
||||
hack_bool("line_numbers")
|
||||
|
||||
return conf_dict
|
||||
|
||||
|
||||
|
||||
|
||||
def save_config(conf_dict):
|
||||
from os.path import join
|
||||
|
||||
cparser = ConfigParser()
|
||||
cparser.add_section(CONF_SECTION)
|
||||
|
||||
for key, val in conf_dict.iteritems():
|
||||
cparser.set(CONF_SECTION, key, val)
|
||||
|
||||
try:
|
||||
outf = open(join(save_config_path("pudb"), "pudb.cfg"), "w")
|
||||
cparser.write(outf)
|
||||
outf.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def edit_config(ui, conf_dict):
|
||||
import urwid
|
||||
|
||||
cb_line_numbers = urwid.CheckBox("Show Line Numbers",
|
||||
bool(conf_dict["line_numbers"]))
|
||||
|
||||
shells = ["classic", "ipython"]
|
||||
|
||||
shell_rb_grp = []
|
||||
shell_rbs = [
|
||||
urwid.RadioButton(shell_rb_grp, name,
|
||||
conf_dict["shell"] == name)
|
||||
for name in shells]
|
||||
|
||||
from pudb.theme import THEMES
|
||||
|
||||
known_theme = conf_dict["theme"] in THEMES
|
||||
|
||||
theme_rb_grp = []
|
||||
theme_edit = urwid.Edit(edit_text=conf_dict["theme"])
|
||||
theme_rbs = [
|
||||
urwid.RadioButton(theme_rb_grp, name,
|
||||
conf_dict["theme"] == name)
|
||||
for name in THEMES]+[
|
||||
urwid.RadioButton(theme_rb_grp, "Custom:",
|
||||
not known_theme),
|
||||
urwid.Padding(
|
||||
urwid.AttrWrap(theme_edit, "value"),
|
||||
left=4),
|
||||
|
||||
urwid.Text("\nTo use a custom theme, see example-theme.py in the "
|
||||
"pudb distribution. Enter the full path to a file like it in the "
|
||||
"box above."),
|
||||
]
|
||||
|
||||
if ui.dialog(
|
||||
urwid.ListBox(
|
||||
[cb_line_numbers]
|
||||
+ [urwid.Text("")]
|
||||
+ [urwid.AttrWrap(urwid.Text("Shell:\n"), "group head")] + shell_rbs
|
||||
+ [urwid.AttrWrap(urwid.Text("\nTheme:\n"), "group head")] + theme_rbs,
|
||||
),
|
||||
[
|
||||
("OK", True),
|
||||
("Cancel", False),
|
||||
],
|
||||
title="Edit Preferences"):
|
||||
for shell, shell_rb in zip(shells, shell_rbs):
|
||||
if shell_rb.get_state():
|
||||
conf_dict["shell"] = shell
|
||||
|
||||
saw_theme = False
|
||||
for theme, theme_rb in zip(THEMES, theme_rbs):
|
||||
if theme_rb.get_state():
|
||||
conf_dict["theme"] = theme
|
||||
saw_theme = True
|
||||
|
||||
if not saw_theme:
|
||||
conf_dict["theme"] = theme_edit.get_edit_text()
|
||||
|
||||
conf_dict["line_numbers"] = cb_line_numbers.get_state()
|
||||
|
||||
|
@ -4,7 +4,6 @@ except ImportError:
|
||||
HAVE_IPYTHON = False
|
||||
else:
|
||||
HAVE_IPYTHON = True
|
||||
USE_IPYTHON = "ask"
|
||||
|
||||
|
||||
|
||||
|
@ -4,11 +4,11 @@ import urwid
|
||||
|
||||
|
||||
class SourceLine(urwid.FlowWidget):
|
||||
def __init__(self, dbg_ui, text, lineno='', attr=None, has_breakpoint=False):
|
||||
def __init__(self, dbg_ui, text, line_nr='', attr=None, has_breakpoint=False):
|
||||
self.dbg_ui = dbg_ui
|
||||
self.text = text
|
||||
self.attr = attr
|
||||
self.lineno = lineno
|
||||
self.line_nr = line_nr
|
||||
self.has_breakpoint = has_breakpoint
|
||||
self.is_current = False
|
||||
self.highlight = False
|
||||
@ -32,6 +32,9 @@ class SourceLine(urwid.FlowWidget):
|
||||
return 1
|
||||
|
||||
def render(self, (maxcol,), focus=False):
|
||||
from pudb import CONFIG
|
||||
render_line_nr = CONFIG["line_numbers"]
|
||||
|
||||
hscroll = self.dbg_ui.source_hscroll_start
|
||||
attrs = []
|
||||
if self.is_current:
|
||||
@ -53,7 +56,9 @@ class SourceLine(urwid.FlowWidget):
|
||||
attrs.append("highlighted")
|
||||
|
||||
if not attrs and self.attr is not None:
|
||||
attr = [("lineno", len(self.lineno))]+self.attr
|
||||
attr = self.attr
|
||||
if render_line_nr:
|
||||
attr = [("line number", len(self.line_nr))] + attr
|
||||
else:
|
||||
attr = [(" ".join(attrs+["source"]), hscroll+maxcol-2)]
|
||||
|
||||
@ -66,7 +71,10 @@ class SourceLine(urwid.FlowWidget):
|
||||
self.dbg_ui.source_hscroll_start,
|
||||
rle_len(attr))
|
||||
|
||||
text = crnt+bp+self.lineno+text
|
||||
if render_line_nr:
|
||||
text = self.line_nr + text
|
||||
|
||||
text = crnt+bp+text
|
||||
attr = [("source", 1), ("bp_star", 1)] + attr
|
||||
|
||||
# clipping ------------------------------------------------------------
|
||||
@ -88,13 +96,13 @@ class SourceLine(urwid.FlowWidget):
|
||||
|
||||
|
||||
def format_source(debugger_ui, lines, breakpoints):
|
||||
lineno_str = "%%%dd "%(len(str(len(lines))))
|
||||
lineno_format = "%%%dd "%(len(str(len(lines))))
|
||||
try:
|
||||
import pygments
|
||||
except ImportError:
|
||||
return [SourceLine(debugger_ui,
|
||||
line.rstrip("\n\r").replace("\t", 8*" "),
|
||||
lineno_str%(i+1), None,
|
||||
lineno_format % (i+1), None,
|
||||
has_breakpoint=i+1 in breakpoints)
|
||||
for i, line in enumerate(lines)]
|
||||
else:
|
||||
@ -143,7 +151,7 @@ def format_source(debugger_ui, lines, breakpoints):
|
||||
result.append(
|
||||
SourceLine(debugger_ui,
|
||||
subself.current_line,
|
||||
lineno_str%subself.lineno,
|
||||
lineno_format % subself.lineno,
|
||||
subself.current_attr,
|
||||
has_breakpoint=subself.lineno in breakpoints))
|
||||
subself.current_line = ""
|
||||
|
@ -1,4 +1,9 @@
|
||||
def get_palette(may_use_fancy_formats):
|
||||
THEMES = ["classic", "vim"]
|
||||
|
||||
|
||||
|
||||
|
||||
def get_palette(may_use_fancy_formats, theme="classic"):
|
||||
if may_use_fancy_formats:
|
||||
def add_setting(color, setting):
|
||||
return color+","+setting
|
||||
@ -6,7 +11,7 @@ def get_palette(may_use_fancy_formats):
|
||||
def add_setting(color, setting):
|
||||
return color
|
||||
|
||||
return [
|
||||
palette = [
|
||||
("header", "black", "light gray", "standout"),
|
||||
|
||||
("breakpoint source", "yellow", "dark red"),
|
||||
@ -75,6 +80,7 @@ def get_palette(may_use_fancy_formats):
|
||||
("label", "black", "light gray"),
|
||||
("value", "yellow", "dark blue"),
|
||||
("fixed value", "light gray", "dark blue"),
|
||||
("group head", add_setting("black", "bold"), "light gray"),
|
||||
|
||||
("search box", "black", "dark cyan"),
|
||||
("search not found", "white", "dark red"),
|
||||
@ -89,7 +95,7 @@ def get_palette(may_use_fancy_formats):
|
||||
("current focused source", "white", "dark cyan"),
|
||||
("current highlighted source", "white", "dark cyan"),
|
||||
|
||||
("lineno", "light gray", "dark blue"),
|
||||
("line number", "light gray", "dark blue"),
|
||||
("keyword", add_setting("white", "bold"), "dark blue"),
|
||||
("name", "light cyan", "dark blue"),
|
||||
("literal", "light magenta", "dark blue"),
|
||||
@ -99,3 +105,37 @@ def get_palette(may_use_fancy_formats):
|
||||
|
||||
]
|
||||
|
||||
palette_dict = dict(
|
||||
(entry[0], entry[1:]) for entry in palette)
|
||||
|
||||
if theme == "classic":
|
||||
pass
|
||||
elif theme == "vim":
|
||||
palette_dict.update({
|
||||
"source": ("black", "default"),
|
||||
"keyword": ("brown", "default"),
|
||||
"kw_namespace": ("dark magenta", "default"),
|
||||
"literal": ("black", "default"),
|
||||
"string": ("dark red", "default"),
|
||||
"punctuation": ("black", "default"),
|
||||
"comment": ("dark blue", "default"),
|
||||
"classname": ("dark cyan", "default"),
|
||||
"name": ("dark cyan", "default"),
|
||||
"line number": ("dark gray", "default"),
|
||||
"bp_star": ("dark red", "default"),
|
||||
})
|
||||
else:
|
||||
try:
|
||||
symbols = {
|
||||
"palette": palette_dict,
|
||||
"add_setting": add_setting,
|
||||
}
|
||||
execfile(theme, symbols)
|
||||
except:
|
||||
print "Error when importing theme:"
|
||||
from traceback import print_exc
|
||||
print_exc()
|
||||
raw_input("Hit enter:")
|
||||
|
||||
return [(key,)+value for key, value in palette_dict.iteritems()]
|
||||
|
||||
|
7
setup.py
7
setup.py
@ -70,12 +70,15 @@ setup(name='pudb',
|
||||
|
||||
python -m pudb.run my-script.py
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
Documentation and Support
|
||||
-------------------------
|
||||
|
||||
PuDB has a `wiki <http://wiki.tiker.net/PuDB>`_, where documentation and
|
||||
debugging wisdom are collected.
|
||||
|
||||
PuDB also has a `mailing list <http://lists.tiker.net/listinfo/pudb>`_ that
|
||||
you may use to submit patches and requests for help.
|
||||
|
||||
Programming PuDB
|
||||
----------------
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user