Support toggling `debug` on and off.

This introduces an `autoupdate` function that runs after the render, forwards
temporary states as needed, and does not trigger any rerenders itself.
1 files changed, 53 insertions(+), 5 deletions(-)

M check.py
M check.py +53 -5
@@ 1,8 1,11 @@ 
 #!/home/sietse/anaconda3/bin/python3.6
 
 import curses
+import re
+from enum import Enum, unique
 from pprint import pformat
 
+
 TASKS = [
     'Workrave',
     'Bril',

          
@@ 18,6 21,13 @@ TASKS = [
     'Status of HPP project',
 ]
 
+@unique
+class Debug(Enum):
+    Show = 1
+    PleaseHide = 2
+    Hidden = 3
+
+
 def color_pair(id, fg, bg):
     """Combine defining a colour pair and returning its reference"""
     curses.init_pair(id, fg, bg)

          
@@ 60,11 70,43 @@ def update(state, key):
     elif key == ' ':
         checked, desc = OS['tasks'][OS['current_task_id']]
         NS['tasks'][OS['current_task_id']] = not checked, desc
+    elif key == 'd':
+        if OS['debug'] is Debug.Show:
+            NS['debug'] = Debug.PleaseHide
+        elif OS['debug'] in (Debug.PleaseHide, Debug.Hidden):
+            NS['debug'] = Debug.Show
     elif key == 'q':
         NS['running'] = False
     return NS
 
 
+def autoupdate_state_no_rerender(state):
+    """Perform state updates that only depend on the previous state, and
+    that do not require a rerender.
+
+    These are the 'automatic' state updates that happen after rendering,
+    regardless of keypresses.
+
+    Rerendering will only happen after the next keypress!
+
+    If this auto_update function ever needs to trigger immediate
+    rerenders, let's not hack about with `rerender` flags; instead,
+    let's switch to a model where both terminal keypresses and messages
+    triggered by auto_update are part of the same message stream / event
+    stream, so neither update loop can block the other.
+    """
+    OS, NS = state, state.copy()  # Old State and New State
+
+    if OS['debug'] is Debug.PleaseHide:
+        # The view() call just before us has processed the PleaseHide
+        # state and wiped the debug info; now we change PleaseHide to
+        # Hidden so that the next cycle does not needlessly re-wipe the
+        # debug info. The change from PleaseHide to Hidden does not
+        # require a rerender.
+        NS['debug'] = Debug.Hidden
+    return NS
+
+
 def checkbox(checked):
     return '[x]' if checked else '[ ]'
 

          
@@ 78,8 120,12 @@ def view(state, t, styles) -> None:
             style = styles['plain']
         t.addstr(i, 0, f'- {checkbox(checked)} {task}', style)
 
-    if state['debug']:
+    if state['debug'] is Debug.Show:
         t.addstr(i + 2, 0, pformat(state), styles['plain'])
+    elif state['debug'] is Debug.PleaseHide:
+        t.addstr(i + 2, 0, re.sub('.', ' ', pformat(state)), styles['plain'])
+    elif state['debug'] is Debug.Hidden:
+        pass
 
 
 def run(t, styles):

          
@@ 87,16 133,18 @@ def run(t, styles):
         'tasks': [(False, t) for t in TASKS],
         'current_task_id': 0,
         'running': True,
-        'debug': False,
+        'debug': Debug.Hidden,
     }
     view(state, t, styles)
     while True:
-        key = t.getkey()
-        state = update(state, key)
-        view(state, t, styles)
+        key = t.getkey()  # Wait for the next keypress...
+        state = update(state, key)  # ...update state based on the key...
+        view(state, t, styles)  # ...render the new state...
+        state = autoupdate_state_no_rerender(state) # ...and do housekeeping.
         if not state['running']:
             break
 
+
 def main():
     with TerminalAndStyles() as (t, styles):
         run(t, styles)