summaryrefslogtreecommitdiff
path: root/alot/widgets
diff options
context:
space:
mode:
authorAnton Khirnov <anton@khirnov.net>2020-05-05 11:23:22 +0200
committerAnton Khirnov <anton@khirnov.net>2020-05-05 11:24:33 +0200
commit8ce767fe63cd8a89f243671ae0ada8975c2c7639 (patch)
treeb50d56ee741cb66715ff5dc3d64a5da25f3031b0 /alot/widgets
parent85f50af0ebde0d07ba47bea1bcf916d692567252 (diff)
thread: add basic support for folding long quoted blocks
Diffstat (limited to 'alot/widgets')
-rw-r--r--alot/widgets/thread.py100
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