diff options
author | Anton Khirnov <anton@khirnov.net> | 2020-05-05 11:23:22 +0200 |
---|---|---|
committer | Anton Khirnov <anton@khirnov.net> | 2020-05-05 11:24:33 +0200 |
commit | 8ce767fe63cd8a89f243671ae0ada8975c2c7639 (patch) | |
tree | b50d56ee741cb66715ff5dc3d64a5da25f3031b0 /alot/widgets | |
parent | 85f50af0ebde0d07ba47bea1bcf916d692567252 (diff) |
thread: add basic support for folding long quoted blocks
Diffstat (limited to 'alot/widgets')
-rw-r--r-- | alot/widgets/thread.py | 100 |
1 files changed, 93 insertions, 7 deletions
diff --git a/alot/widgets/thread.py b/alot/widgets/thread.py index b410ff14..dd179ed9 100644 --- a/alot/widgets/thread.py +++ b/alot/widgets/thread.py @@ -75,10 +75,30 @@ class _MIMEPartWidget(urwid.WidgetWrap): # when not None, a list of child _MIMEPartWidget subclasses _children = None + _fold_level = None + def __init__(self, body_wgt, children = None): self._children = children + self._fold_level = 0 super().__init__(body_wgt) + @property + def foldlevel(self): + return self._fold_level + @foldlevel.setter + def foldlevel(self, val): + if self._children is None: + return + + val_in = max(0, val) + val_out = 0 + + for ch in self._children: + ch.foldlevel = val_in + val_out = max(val_out, ch.foldlevel) + + self._fold_level = val_out + class _CryptPartWidget(_MIMEPartWidget): def __init__(self, mime_tree, alternative_pref): children = None @@ -132,17 +152,53 @@ class _AltMixedPart(_MIMEPartWidget): def __init__(self, children): super().__init__(urwid.Pile(children), children) +class _TextBlock(urwid.WidgetWrap): + + _level = None + + _switch_wgt = None + _text_wgt = None + _folded_wgt = None + + def __init__(self, level, lines, attr, fold_context): + self._level = level + self._text_wgt = urwid.Text((attr, '\n'.join(lines))) + + context_start, context_end = fold_context + if level == 0 or len(lines) <= context_start + context_end + 1: + # block is too small for folding + self._folded_wgt = self._text_wgt + else: + folded_lines = lines[:context_start] + folded_lines.append('%s [...%d lines folded...]' % + ('> ' * level, len(lines) - context_start - context_end)) + folded_lines.extend(lines[-context_end:]) + self._folded_wgt = urwid.Text((attr, '\n'.join(folded_lines))) + + self._switch_wgt = urwid.WidgetPlaceholder(self._text_wgt) + + super().__init__(self._switch_wgt) + + def set_foldlevel(self, level): + wgt = self._text_wgt if level >= self._level else self._folded_wgt + self._switch_wgt.original_widget = wgt + class _TextPart(_MIMEPartWidget): _QUOTE_CHARS = '>|:}#' _QUOTE_REGEX = '(([ \t]*[{quote_chars}])+)'.format(quote_chars = _QUOTE_CHARS) + _max_level = None + _block_wgts = None + def __init__(self, text): attr_text = settings.get_theming_attribute('thread', 'body') attrs_quote = settings.get_quote_theming() + fold_context = settings.get('thread_fold_context') lines = text.splitlines() - quote_levels = [] + blocks = [] + max_level = 0 for line in lines: level = 0 m = re.match(self._QUOTE_REGEX, line) @@ -151,18 +207,39 @@ class _TextPart(_MIMEPartWidget): for c in self._QUOTE_CHARS: level += g.count(c) - quote_levels.append(level) + max_level = max(max_level, level) - line_widgets = [] + if len(blocks) > 0 and blocks[-1][0] == level: + blocks[-1][1].append(line) + else: + blocks.append((level, [line])) - for level, line in zip(quote_levels, lines): + block_wgts = [] + for level, lines in blocks: if level == 0 or len(attrs_quote) < 1: attr = attr_text else: attr = attrs_quote[(level - 1) % len(attrs_quote)] - line_widgets.append(urwid.Text((attr, line))) - super().__init__(urwid.Pile(line_widgets)) + wgt = _TextBlock(level, lines, attr, fold_context) + block_wgts.append(wgt) + + self._block_wgts = block_wgts + self._max_level = max_level + + super().__init__(urwid.Pile(block_wgts)) + + @property + def foldlevel(self): + return self._fold_level + @foldlevel.setter + def foldlevel(self, val): + val = max(0, min(self._max_level, val)) + logging.info('settting foldlevel to %d', val) + + for b in self._block_wgts: + b.set_foldlevel(val) + self._fold_level = val class _EmptyMessageWidget(_MIMEPartWidget): def __init__(self): @@ -201,7 +278,7 @@ def _render_mime_tree(mime_tree, alternative_pref): if mime_tree.is_alternative: return _handle_alternative(mime_tree, alternative_pref) - return _AltMixedPart(mime_tree, alternative_pref) + return _handle_mixed(mime_tree, alternative_pref) # no children - this is a leaf node # skip attachment parts @@ -339,6 +416,8 @@ class MessageWidget(urwid.WidgetWrap): self.display_all_headers = False self.display_source = False + self.foldlevel = settings.get('thread_fold_initial_level') + def get_message(self): return self._message @@ -425,6 +504,13 @@ class MessageWidget(urwid.WidgetWrap): self._reassemble(val) self._display_source = val + @property + def foldlevel(self): + return self._body_wgt.foldlevel + @foldlevel.setter + def foldlevel(self, val): + self._body_wgt.foldlevel = val + def get_selected_attachment(self): """ If an AttachmentWidget is currently focused, return it. Otherwise return |