codekingpro/portable-devtools
114k
1from __future__ import annotations2 3import pprint4import textwrap5import typing6from collections.abc import Sequence7from typing import Optional8 9import urwid10 11from mitmproxy import exceptions12from mitmproxy import optmanager13from mitmproxy.tools.console import layoutwidget14from mitmproxy.tools.console import overlay15from mitmproxy.tools.console import signals16 17HELP_HEIGHT = 518 19 20def can_edit_inplace(opt):21 if opt.choices:22 return False23 if opt.typespec in [str, int, Optional[str], Optional[int]]:24 return True25 26 27class OptionItem(urwid.WidgetWrap):28 def __init__(self, walker, opt, focused, namewidth, editing):29 self.walker, self.opt, self.focused = walker, opt, focused30 self.namewidth = namewidth31 self.editing = editing32 super().__init__(self.get_widget())33 34 def get_widget(self):35 val = self.opt.current()36 if self.opt.typespec is bool:37 displayval = "true" if val else "false"38 elif not val:39 displayval = ""40 elif self.opt.typespec == Sequence[str]:41 displayval = pprint.pformat(val, indent=1)42 else:43 displayval = str(val)44 45 changed = self.walker.master.options.has_changed(self.opt.name)46 if self.focused:47 valstyle = "option_active_selected" if changed else "option_selected"48 else:49 valstyle = "option_active" if changed else "text"50 51 if self.editing:52 valw = urwid.Edit(edit_text=displayval)53 else:54 valw = urwid.AttrMap(55 urwid.Padding(urwid.Text([(valstyle, displayval)])), valstyle56 )57 58 return urwid.Columns(59 [60 (61 self.namewidth,62 urwid.Text([("title", self.opt.name.ljust(self.namewidth))]),63 ),64 valw,65 ],66 dividechars=2,67 focus_column=1,68 )69 70 def get_edit_text(self):71 return self._w[1].get_edit_text()72 73 def selectable(self):74 return True75 76 def keypress(self, size, key):77 if self.editing:78 self._w[1].keypress(size, key)79 return80 return key81 82 83class OptionListWalker(urwid.ListWalker):84 def __init__(self, master, help_widget: OptionHelp):85 self.master = master86 self.help_widget = help_widget87 88 self.index = 089 self.focusobj = None90 91 self.opts = sorted(master.options.keys())92 self.maxlen = max(len(i) for i in self.opts)93 self.editing = False94 self.set_focus(0)95 self.master.options.changed.connect(self.sig_mod)96 97 def sig_mod(self, *args, **kwargs):98 self.opts = sorted(self.master.options.keys())99 self.maxlen = max(len(i) for i in self.opts)100 self._modified()101 self.set_focus(self.index)102 103 def start_editing(self):104 self.editing = True105 self.focus_obj = self._get(self.index, True)106 self._modified()107 108 def stop_editing(self):109 self.editing = False110 self.focus_obj = self._get(self.index, False)111 self.set_focus(self.index)112 self._modified()113 114 def get_edit_text(self):115 return self.focus_obj.get_edit_text()116 117 def _get(self, pos, editing):118 name = self.opts[pos]119 opt = self.master.options._options[name]120 return OptionItem(self, opt, pos == self.index, self.maxlen, editing)121 122 def get_focus(self):123 return self.focus_obj, self.index124 125 def set_focus(self, index):126 self.editing = False127 name = self.opts[index]128 opt = self.master.options._options[name]129 self.index = index130 self.focus_obj = self._get(self.index, self.editing)131 self.help_widget.update_help_text(opt.help)132 self._modified()133 134 def get_next(self, pos):135 if pos >= len(self.opts) - 1:136 return None, None137 pos = pos + 1138 return self._get(pos, False), pos139 140 def get_prev(self, pos):141 pos = pos - 1142 if pos < 0:143 return None, None144 return self._get(pos, False), pos145 146 def positions(self, reverse=False):147 if reverse:148 return reversed(range(len(self.opts)))149 else:150 return range(len(self.opts))151 152 153class OptionsList(urwid.ListBox):154 def __init__(self, master, help_widget: OptionHelp):155 self.master = master156 self.walker = OptionListWalker(master, help_widget)157 super().__init__(self.walker)158 159 def save_config(self, path):160 try:161 optmanager.save(self.master.options, path)162 except exceptions.OptionsError as e:163 signals.status_message.send(message=str(e))164 165 def keypress(self, size, key):166 if self.walker.editing:167 if key == "enter":168 foc, idx = self.get_focus()169 v = self.walker.get_edit_text()170 try:171 self.master.options.set(f"{foc.opt.name}={v}")172 except exceptions.OptionsError as v:173 signals.status_message.send(message=str(v))174 self.walker.stop_editing()175 return None176 elif key == "esc":177 self.walker.stop_editing()178 return None179 else:180 if key == "m_start":181 self.set_focus(0)182 self.walker._modified()183 elif key == "m_end":184 self.set_focus(len(self.walker.opts) - 1)185 self.walker._modified()186 elif key == "m_select":187 foc, idx = self.get_focus()188 if foc.opt.typespec is bool:189 self.master.options.toggler(foc.opt.name)()190 # Bust the focus widget cache191 self.set_focus(self.walker.index)192 elif can_edit_inplace(foc.opt):193 self.walker.start_editing()194 self.walker._modified()195 elif foc.opt.choices:196 self.master.overlay(197 overlay.Chooser(198 self.master,199 foc.opt.name,200 foc.opt.choices,201 foc.opt.current(),202 self.master.options.setter(foc.opt.name),203 )204 )205 elif foc.opt.typespec in (Sequence[str], typing.Sequence[str]):206 self.master.overlay(207 overlay.OptionsOverlay(208 self.master,209 foc.opt.name,210 foc.opt.current(),211 HELP_HEIGHT + 5,212 ),213 valign="top",214 )215 else:216 raise NotImplementedError()217 return super().keypress(size, key)218 219 220class OptionHelp(urwid.Frame):221 def __init__(self, master):222 self.master = master223 super().__init__(self.widget(""))224 self.set_active(False)225 226 def set_active(self, val):227 h = urwid.Text("Option Help")228 style = "heading" if val else "heading_inactive"229 self.header = urwid.AttrMap(h, style)230 231 def widget(self, txt):232 cols, _ = self.master.ui.get_cols_rows()233 return urwid.ListBox([urwid.Text(i) for i in textwrap.wrap(txt, cols)])234 235 def update_help_text(self, txt: str) -> None:236 self.body = self.widget(txt)237 238 239class Options(urwid.Pile, layoutwidget.LayoutWidget):240 title = "Options"241 keyctx = "options"242 243 focus_position: int244 245 def __init__(self, master):246 oh = OptionHelp(master)247 self.optionslist = OptionsList(master, oh)248 super().__init__(249 [250 self.optionslist,251 (HELP_HEIGHT, oh),252 ]253 )254 self.master = master255 256 def current_name(self):257 foc, idx = self.optionslist.get_focus()258 return foc.opt.name259 260 def keypress(self, size, key):261 if key == "m_next":262 self.focus_position = (self.focus_position + 1) % len(self.widget_list)263 self.widget_list[1].set_active(self.focus_position == 1)264 key = None265 266 # This is essentially a copypasta from urwid.Pile's keypress handler.267 # So much for "closed for modification, but open for extension".268 item_rows = None269 if len(size) == 2:270 item_rows = self.get_item_rows(size, focus=True)271 tsize = self.get_item_size(size, self.focus_position, True, item_rows)272 return self.focus.keypress(tsize, key)273 