We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[dev 2.0+] disp_rstatus_fmt causes halting exception on GraphSheet
**Small description**
I modified the `disp_rstatus_fmt` to provide information I require when working with sheets, however when using GraphSheet vd crashes with the following exception. I currently have to use `^g` to see the same information as I would be able to see using this `disp_rstatus_fmt`.

`options.disp_rstatus_fmt = ' {sheet.cursorRowIndex}/{sheet.nRows}[{sheet.nSelectedRows}] {sheet.rowtype} '`

`AttributeError: 'GraphSheet' object has no attribute 'cursorRowIndex'`

**Expected result**
GraphSheet to load

FWIW, I was also attempting to make a % counter in `disp_rstatus_fmt` however I get some odd other exceptions (this isn't really an issue so I haven't opened a separate bug, however this was my original use-case).

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `visidata/utils.py`
Content:
```
1 import operator
2 
3 'Various helper classes and functions.'
4 
5 __all__ = ['AlwaysDict', 'AttrDict', 'moveListItem', 'namedlist', 'classproperty']
6 
7 
8 class AlwaysDict(dict):
9     'return same val for all keys'
10     def __init__(self, val, **kwargs):
11         super().__init__(**kwargs)
12         self._val = val
13 
14     def __getitem__(self, k):
15         return self._val
16 
17 class AttrDict(dict):
18     'Augment a dict with more convenient .attr syntax.  not-present keys return None.'
19     def __getattr__(self, k):
20         try:
21             return self[k]
22         except KeyError:
23             if k.startswith("__"):
24                 raise AttributeError
25             return None
26 
27     def __setattr__(self, k, v):
28         self[k] = v
29 
30     def __dir__(self):
31         return self.keys()
32 
33 
34 class classproperty(property):
35     def __get__(self, cls, obj):
36         return classmethod(self.fget).__get__(None, obj or cls)()
37 
38 
39 def moveListItem(L, fromidx, toidx):
40     "Move element within list `L` and return element's new index."
41     toidx = min(max(toidx, 0), len(L)-1)
42     fromidx = min(max(fromidx, 0), len(L)-1)
43     r = L.pop(fromidx)
44     L.insert(toidx, r)
45     return toidx
46 
47 
48 class OnExit:
49     '"with OnExit(func, ...):" calls func(...) when the context is exited'
50     def __init__(self, func, *args, **kwargs):
51         self.func = func
52         self.args = args
53         self.kwargs = kwargs
54 
55     def __enter__(self):
56         return self
57 
58     def __exit__(self, exc_type, exc_value, exc_traceback):
59         try:
60             self.func(*self.args, **self.kwargs)
61         except Exception as e:
62             vd.exceptionCaught(e)
63 
64 
65 def itemsetter(i):
66     def g(obj, v):
67         obj[i] = v
68     return g
69 
70 
71 def namedlist(objname, fieldnames):
72     'like namedtuple but editable'
73     class NamedListTemplate(list):
74         __name__ = objname
75         _fields = fieldnames
76 
77         def __init__(self, L=None, **kwargs):
78             if L is None:
79                 L = [None]*len(self._fields)
80             elif len(L) < len(self._fields):
81                 L.extend([None]*(len(self._fields) - len(L)))
82             super().__init__(L)
83             for k, v in kwargs.items():
84                 setattr(self, k, v)
85 
86         def __getattr__(self, k):
87             'to enable .fieldname'
88             try:
89                 return self[self._fields.index(k)]
90             except ValueError:
91                 raise AttributeError
92 
93         def __setattr__(self, k, v):
94             'to enable .fieldname ='
95             try:
96                 self[self._fields.index(k)] = v
97             except ValueError:
98                 super().__setattr__(k, v)
99 
100     return NamedListTemplate
101
```

Path: `visidata/statusbar.py`
Content:
```
1 import collections
2 import curses
3 
4 from visidata import vd, VisiData, BaseSheet, Sheet, ColumnItem, Column, RowColorizer, options, colors, wrmap, clipdraw, ExpectedException, update_attr, theme
5 
6 
7 __all__ = ['StatusSheet', 'status', 'error', 'fail', 'warning', 'debug']
8 
9 theme('disp_rstatus_fmt', ' {sheet.longname} {sheet.nRows:9d} {sheet.rowtype} ', 'right-side status format string')
10 theme('disp_status_fmt', '{sheet.shortcut}› {sheet.name}| ', 'status line prefix')
11 theme('disp_lstatus_max', 0, 'maximum length of left status line')
12 theme('disp_status_sep', ' | ', 'separator between statuses')
13 
14 theme('color_keystrokes', 'white', 'color of input keystrokes on status line')
15 theme('color_status', 'bold', 'status line color')
16 theme('color_error', 'red', 'error message color')
17 theme('color_warning', 'yellow', 'warning message color')
18 theme('color_top_status', 'underline', 'top window status bar color')
19 theme('color_active_status', 'bold', ' active window status bar color')
20 theme('color_inactive_status', '8', 'inactive window status bar color')
21 
22 BaseSheet.init('longname', lambda: '')
23 
24 vd.beforeExecHooks.append(lambda sheet, cmd, args, ks: setattr(sheet, 'longname', cmd.longname))
25 
26 @VisiData.lazy_property
27 def statuses(vd):
28     return collections.OrderedDict()  # (priority, statusmsg) -> num_repeats; shown until next action
29 
30 
31 @VisiData.lazy_property
32 def statusHistory(vd):
33     return list()  # list of [priority, statusmsg, repeats] for all status messages ever
34 
35 
36 @VisiData.global_api
37 def status(self, *args, priority=0):
38     'Display *args* on status until next action.'
39     if not args:
40         return True
41 
42     k = (priority, tuple(map(str, args)))
43     self.statuses[k] = self.statuses.get(k, 0) + 1
44 
45     if self.statusHistory:
46         prevpri, prevargs, prevn = self.statusHistory[-1]
47         if prevpri == priority and prevargs == args:
48             self.statusHistory[-1][2] += 1
49             return True
50 
51     self.statusHistory.append([priority, args, 1])
52     return True
53 
54 @VisiData.global_api
55 def error(vd, *args):
56     'Abort with ExpectedException, and display *args* on status as an error.'
57     vd.status(*args, priority=3)
58     raise ExpectedException(args[0] if args else '')
59 
60 @VisiData.global_api
61 def fail(vd, *args):
62     'Abort with ExpectedException, and display *args* on status as a warning.'
63     vd.status(*args, priority=2)
64     raise ExpectedException(args[0] if args else '')
65 
66 @VisiData.global_api
67 def warning(vd, *args):
68     'Display *args* on status as a warning.'
69     vd.status(*args, priority=1)
70 
71 @VisiData.global_api
72 def debug(vd, *args, **kwargs):
73     'Display *args* on status if options.debug is set.'
74     if options.debug:
75         return vd.status(*args, **kwargs)
76 
77 def middleTruncate(s, w):
78     if len(s) <= w:
79         return s
80     return s[:w] + options.disp_truncator + s[-w:]
81 
82 
83 def composeStatus(msgparts, n):
84     msg = '; '.join(wrmap(str, msgparts))
85     if n > 1:
86         msg = '[%sx] %s' % (n, msg)
87     return msg
88 
89 
90 @BaseSheet.api
91 def leftStatus(sheet):
92     'Return left side of status bar for this sheet. Overridable.'
93     return options.disp_status_fmt.format(sheet=sheet, vd=vd)
94 
95 
96 @VisiData.api
97 def drawLeftStatus(vd, scr, vs):
98     'Draw left side of status bar.'
99     cattr = colors.get_color('color_status')
100     active = (vs is vd.sheets[0]) if vd.sheets else False # active sheet
101     if active:
102         cattr = update_attr(cattr, colors.color_active_status, 0)
103     else:
104         cattr = update_attr(cattr, colors.color_inactive_status, 0)
105 
106     if scr is vd.winTop:
107         cattr = update_attr(cattr, colors.color_top_status, 1)
108 
109     attr = cattr.attr
110     error_attr = update_attr(cattr, colors.color_error, 1).attr
111     warn_attr = update_attr(cattr, colors.color_warning, 2).attr
112     sep = options.disp_status_sep
113 
114     x = 0
115     y = vs.windowHeight-1  # status for each window
116     try:
117         lstatus = vs.leftStatus()
118         maxwidth = options.disp_lstatus_max
119         if maxwidth > 0:
120             lstatus = middleTruncate(lstatus, maxwidth//2)
121 
122         x = clipdraw(scr, y, 0, lstatus, attr, w=vs.windowWidth-1)
123 
124         vd.onMouse(scr, y, 0, 1, x,
125                         BUTTON1_PRESSED='sheets',
126                         BUTTON3_PRESSED='rename-sheet',
127                         BUTTON3_CLICKED='rename-sheet')
128     except Exception as e:
129         vd.exceptionCaught(e)
130 
131     if not active:
132         return
133 
134     one = False
135     for (pri, msgparts), n in sorted(vd.statuses.items(), key=lambda k: -k[0][0]):
136         try:
137             if x > vs.windowWidth:
138                 break
139             if one:  # any messages already:
140                 x += clipdraw(scr, y, x, sep, attr, w=vs.windowWidth-x)
141             one = True
142             msg = composeStatus(msgparts, n)
143 
144             if pri == 3: msgattr = error_attr
145             elif pri == 2: msgattr = warn_attr
146             elif pri == 1: msgattr = warn_attr
147             else: msgattr = attr
148             x += clipdraw(scr, y, x, msg, msgattr, w=vs.windowWidth-x)
149         except Exception as e:
150             vd.exceptionCaught(e)
151 
152 
153 @VisiData.api
154 def rightStatus(vd, sheet):
155     'Return right side of status bar.  Overrideable.'
156     return options.disp_rstatus_fmt.format(sheet=sheet, vd=vd)
157 
158 
159 @VisiData.api
160 def drawRightStatus(vd, scr, vs):
161     'Draw right side of status bar.  Return length displayed.'
162     rightx = vs.windowWidth
163 
164     ret = 0
165     statcolors = [
166         (vd.rightStatus(vs), 'color_status'),
167     ]
168 
169     active = vs is vd.activeSheet
170 
171     if active:
172         statcolors.append((vd.keystrokes or '', 'color_keystrokes'))
173 
174     if vs.currentThreads:
175         statcolors.insert(0, vd.checkMemoryUsage())
176         if vs.progresses:
177             gerund = vs.progresses[0].gerund
178         else:
179             gerund = 'processing'
180         statcolors.insert(1, ('  %s %s…' % (vs.progressPct, gerund), 'color_working'))
181 
182     if active and vd.currentReplay:
183         statcolors.insert(0, (vd.replayStatus, 'color_status_replay'))
184 
185     for rstatcolor in statcolors:
186         if rstatcolor:
187             try:
188                 rstatus, coloropt = rstatcolor
189                 rstatus = ' '+rstatus
190                 cattr = colors.get_color(coloropt)
191                 if scr is vd.winTop:
192                     cattr = update_attr(cattr, colors.color_top_status, 0)
193                 if active:
194                     cattr = update_attr(cattr, colors.color_active_status, 0)
195                 else:
196                     cattr = update_attr(cattr, colors.color_inactive_status, 0)
197                 statuslen = clipdraw(scr, vs.windowHeight-1, rightx, rstatus, cattr.attr, w=vs.windowWidth-1, rtl=True)
198                 rightx -= statuslen
199                 ret += statuslen
200             except Exception as e:
201                 vd.exceptionCaught(e)
202 
203     if scr:
204         curses.doupdate()
205     return ret
206 
207 
208 class StatusSheet(Sheet):
209     precious = False
210     rowtype = 'statuses'  # rowdef: (priority, args, nrepeats)
211     columns = [
212         ColumnItem('priority', 0, type=int, width=0),
213         ColumnItem('nrepeats', 2, type=int, width=0),
214         ColumnItem('args', 1, width=0),
215         Column('message', getter=lambda col,row: composeStatus(row[1], row[2])),
216     ]
217     colorizers = [
218         RowColorizer(1, 'color_error', lambda s,c,r,v: r and r[0] == 3),
219         RowColorizer(1, 'color_warning', lambda s,c,r,v: r and r[0] in [1,2]),
220     ]
221 
222     def reload(self):
223         self.rows = self.source
224 
225 
226 @VisiData.property
227 def statusHistorySheet(vd):
228     return StatusSheet("status_history", source=vd.statusHistory[::-1])  # in reverse order
229 
230 
231 BaseSheet.addCommand('^P', 'open-statuses', 'vd.push(vd.statusHistorySheet)', 'open Status History')
232
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/visidata/statusbar.py b/visidata/statusbar.py
--- a/visidata/statusbar.py
+++ b/visidata/statusbar.py
@@ -1,7 +1,7 @@
 import collections
 import curses
 
-from visidata import vd, VisiData, BaseSheet, Sheet, ColumnItem, Column, RowColorizer, options, colors, wrmap, clipdraw, ExpectedException, update_attr, theme
+from visidata import vd, VisiData, BaseSheet, Sheet, ColumnItem, Column, RowColorizer, options, colors, wrmap, clipdraw, ExpectedException, update_attr, theme, MissingAttrFormatter
 
 
 __all__ = ['StatusSheet', 'status', 'error', 'fail', 'warning', 'debug']
@@ -153,7 +153,7 @@
 @VisiData.api
 def rightStatus(vd, sheet):
     'Return right side of status bar.  Overrideable.'
-    return options.disp_rstatus_fmt.format(sheet=sheet, vd=vd)
+    return MissingAttrFormatter().format(sheet.options.disp_rstatus_fmt, sheet=sheet, vd=vd)
 
 
 @VisiData.api
diff --git a/visidata/utils.py b/visidata/utils.py
--- a/visidata/utils.py
+++ b/visidata/utils.py
@@ -1,8 +1,9 @@
 import operator
+import string
 
 'Various helper classes and functions.'
 
-__all__ = ['AlwaysDict', 'AttrDict', 'moveListItem', 'namedlist', 'classproperty']
+__all__ = ['AlwaysDict', 'AttrDict', 'moveListItem', 'namedlist', 'classproperty', 'MissingAttrFormatter']
 
 
 class AlwaysDict(dict):
@@ -98,3 +99,17 @@
                 super().__setattr__(k, v)
 
     return NamedListTemplate
+
+class MissingAttrFormatter(string.Formatter):
+    "formats {} fields with `''`, that would normally result in a raised KeyError or AttributeError; intended for user customisable format strings."
+    def get_field(self, field_name, *args, **kwargs):
+        try:
+            return super().get_field(field_name, *args, **kwargs)
+        except (KeyError, AttributeError):
+            return (None, field_name)
+
+    def format_field(self, value, format_spec):
+        # value is missing
+        if not value:
+            return ''
+        return super().format_field(value, format_spec)

{"golden_diff": "diff --git a/visidata/statusbar.py b/visidata/statusbar.py\n--- a/visidata/statusbar.py\n+++ b/visidata/statusbar.py\n@@ -1,7 +1,7 @@\n import collections\n import curses\n \n-from visidata import vd, VisiData, BaseSheet, Sheet, ColumnItem, Column, RowColorizer, options, colors, wrmap, clipdraw, ExpectedException, update_attr, theme\n+from visidata import vd, VisiData, BaseSheet, Sheet, ColumnItem, Column, RowColorizer, options, colors, wrmap, clipdraw, ExpectedException, update_attr, theme, MissingAttrFormatter\n \n \n __all__ = ['StatusSheet', 'status', 'error', 'fail', 'warning', 'debug']\n@@ -153,7 +153,7 @@\n @VisiData.api\n def rightStatus(vd, sheet):\n     'Return right side of status bar.  Overrideable.'\n-    return options.disp_rstatus_fmt.format(sheet=sheet, vd=vd)\n+    return MissingAttrFormatter().format(sheet.options.disp_rstatus_fmt, sheet=sheet, vd=vd)\n \n \n @VisiData.api\ndiff --git a/visidata/utils.py b/visidata/utils.py\n--- a/visidata/utils.py\n+++ b/visidata/utils.py\n@@ -1,8 +1,9 @@\n import operator\n+import string\n \n 'Various helper classes and functions.'\n \n-__all__ = ['AlwaysDict', 'AttrDict', 'moveListItem', 'namedlist', 'classproperty']\n+__all__ = ['AlwaysDict', 'AttrDict', 'moveListItem', 'namedlist', 'classproperty', 'MissingAttrFormatter']\n \n \n class AlwaysDict(dict):\n@@ -98,3 +99,17 @@\n                 super().__setattr__(k, v)\n \n     return NamedListTemplate\n+\n+class MissingAttrFormatter(string.Formatter):\n+    \"formats {} fields with `''`, that would normally result in a raised KeyError or AttributeError; intended for user customisable format strings.\"\n+    def get_field(self, field_name, *args, **kwargs):\n+        try:\n+            return super().get_field(field_name, *args, **kwargs)\n+        except (KeyError, AttributeError):\n+            return (None, field_name)\n+\n+    def format_field(self, value, format_spec):\n+        # value is missing\n+        if not value:\n+            return ''\n+        return super().format_field(value, format_spec)\n", "issue": "[dev 2.0+] disp_rstatus_fmt causes halting exception on GraphSheet\n**Small description**\r\nI modified the `disp_rstatus_fmt` to provide information I require when working with sheets, however when using GraphSheet vd crashes with the following exception. I currently have to use `^g` to see the same information as I would be able to see using this `disp_rstatus_fmt`.\r\n\r\n`options.disp_rstatus_fmt = ' {sheet.cursorRowIndex}/{sheet.nRows}[{sheet.nSelectedRows}] {sheet.rowtype} '`\r\n\r\n`AttributeError: 'GraphSheet' object has no attribute 'cursorRowIndex'`\r\n\r\n**Expected result**\r\nGraphSheet to load\r\n\r\nFWIW, I was also attempting to make a % counter in `disp_rstatus_fmt` however I get some odd other exceptions (this isn't really an issue so I haven't opened a separate bug, however this was my original use-case).\n", "before_files": [{"content": "import operator\n\n'Various helper classes and functions.'\n\n__all__ = ['AlwaysDict', 'AttrDict', 'moveListItem', 'namedlist', 'classproperty']\n\n\nclass AlwaysDict(dict):\n    'return same val for all keys'\n    def __init__(self, val, **kwargs):\n        super().__init__(**kwargs)\n        self._val = val\n\n    def __getitem__(self, k):\n        return self._val\n\nclass AttrDict(dict):\n    'Augment a dict with more convenient .attr syntax.  not-present keys return None.'\n    def __getattr__(self, k):\n        try:\n            return self[k]\n        except KeyError:\n            if k.startswith(\"__\"):\n                raise AttributeError\n            return None\n\n    def __setattr__(self, k, v):\n        self[k] = v\n\n    def __dir__(self):\n        return self.keys()\n\n\nclass classproperty(property):\n    def __get__(self, cls, obj):\n        return classmethod(self.fget).__get__(None, obj or cls)()\n\n\ndef moveListItem(L, fromidx, toidx):\n    \"Move element within list `L` and return element's new index.\"\n    toidx = min(max(toidx, 0), len(L)-1)\n    fromidx = min(max(fromidx, 0), len(L)-1)\n    r = L.pop(fromidx)\n    L.insert(toidx, r)\n    return toidx\n\n\nclass OnExit:\n    '\"with OnExit(func, ...):\" calls func(...) when the context is exited'\n    def __init__(self, func, *args, **kwargs):\n        self.func = func\n        self.args = args\n        self.kwargs = kwargs\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self, exc_type, exc_value, exc_traceback):\n        try:\n            self.func(*self.args, **self.kwargs)\n        except Exception as e:\n            vd.exceptionCaught(e)\n\n\ndef itemsetter(i):\n    def g(obj, v):\n        obj[i] = v\n    return g\n\n\ndef namedlist(objname, fieldnames):\n    'like namedtuple but editable'\n    class NamedListTemplate(list):\n        __name__ = objname\n        _fields = fieldnames\n\n        def __init__(self, L=None, **kwargs):\n            if L is None:\n                L = [None]*len(self._fields)\n            elif len(L) < len(self._fields):\n                L.extend([None]*(len(self._fields) - len(L)))\n            super().__init__(L)\n            for k, v in kwargs.items():\n                setattr(self, k, v)\n\n        def __getattr__(self, k):\n            'to enable .fieldname'\n            try:\n                return self[self._fields.index(k)]\n            except ValueError:\n                raise AttributeError\n\n        def __setattr__(self, k, v):\n            'to enable .fieldname ='\n            try:\n                self[self._fields.index(k)] = v\n            except ValueError:\n                super().__setattr__(k, v)\n\n    return NamedListTemplate\n", "path": "visidata/utils.py"}, {"content": "import collections\nimport curses\n\nfrom visidata import vd, VisiData, BaseSheet, Sheet, ColumnItem, Column, RowColorizer, options, colors, wrmap, clipdraw, ExpectedException, update_attr, theme\n\n\n__all__ = ['StatusSheet', 'status', 'error', 'fail', 'warning', 'debug']\n\ntheme('disp_rstatus_fmt', ' {sheet.longname} {sheet.nRows:9d} {sheet.rowtype} ', 'right-side status format string')\ntheme('disp_status_fmt', '{sheet.shortcut}\u203a {sheet.name}| ', 'status line prefix')\ntheme('disp_lstatus_max', 0, 'maximum length of left status line')\ntheme('disp_status_sep', ' | ', 'separator between statuses')\n\ntheme('color_keystrokes', 'white', 'color of input keystrokes on status line')\ntheme('color_status', 'bold', 'status line color')\ntheme('color_error', 'red', 'error message color')\ntheme('color_warning', 'yellow', 'warning message color')\ntheme('color_top_status', 'underline', 'top window status bar color')\ntheme('color_active_status', 'bold', ' active window status bar color')\ntheme('color_inactive_status', '8', 'inactive window status bar color')\n\nBaseSheet.init('longname', lambda: '')\n\nvd.beforeExecHooks.append(lambda sheet, cmd, args, ks: setattr(sheet, 'longname', cmd.longname))\n\n@VisiData.lazy_property\ndef statuses(vd):\n    return collections.OrderedDict()  # (priority, statusmsg) -> num_repeats; shown until next action\n\n\n@VisiData.lazy_property\ndef statusHistory(vd):\n    return list()  # list of [priority, statusmsg, repeats] for all status messages ever\n\n\n@VisiData.global_api\ndef status(self, *args, priority=0):\n    'Display *args* on status until next action.'\n    if not args:\n        return True\n\n    k = (priority, tuple(map(str, args)))\n    self.statuses[k] = self.statuses.get(k, 0) + 1\n\n    if self.statusHistory:\n        prevpri, prevargs, prevn = self.statusHistory[-1]\n        if prevpri == priority and prevargs == args:\n            self.statusHistory[-1][2] += 1\n            return True\n\n    self.statusHistory.append([priority, args, 1])\n    return True\n\n@VisiData.global_api\ndef error(vd, *args):\n    'Abort with ExpectedException, and display *args* on status as an error.'\n    vd.status(*args, priority=3)\n    raise ExpectedException(args[0] if args else '')\n\n@VisiData.global_api\ndef fail(vd, *args):\n    'Abort with ExpectedException, and display *args* on status as a warning.'\n    vd.status(*args, priority=2)\n    raise ExpectedException(args[0] if args else '')\n\n@VisiData.global_api\ndef warning(vd, *args):\n    'Display *args* on status as a warning.'\n    vd.status(*args, priority=1)\n\n@VisiData.global_api\ndef debug(vd, *args, **kwargs):\n    'Display *args* on status if options.debug is set.'\n    if options.debug:\n        return vd.status(*args, **kwargs)\n\ndef middleTruncate(s, w):\n    if len(s) <= w:\n        return s\n    return s[:w] + options.disp_truncator + s[-w:]\n\n\ndef composeStatus(msgparts, n):\n    msg = '; '.join(wrmap(str, msgparts))\n    if n > 1:\n        msg = '[%sx] %s' % (n, msg)\n    return msg\n\n\n@BaseSheet.api\ndef leftStatus(sheet):\n    'Return left side of status bar for this sheet. Overridable.'\n    return options.disp_status_fmt.format(sheet=sheet, vd=vd)\n\n\n@VisiData.api\ndef drawLeftStatus(vd, scr, vs):\n    'Draw left side of status bar.'\n    cattr = colors.get_color('color_status')\n    active = (vs is vd.sheets[0]) if vd.sheets else False # active sheet\n    if active:\n        cattr = update_attr(cattr, colors.color_active_status, 0)\n    else:\n        cattr = update_attr(cattr, colors.color_inactive_status, 0)\n\n    if scr is vd.winTop:\n        cattr = update_attr(cattr, colors.color_top_status, 1)\n\n    attr = cattr.attr\n    error_attr = update_attr(cattr, colors.color_error, 1).attr\n    warn_attr = update_attr(cattr, colors.color_warning, 2).attr\n    sep = options.disp_status_sep\n\n    x = 0\n    y = vs.windowHeight-1  # status for each window\n    try:\n        lstatus = vs.leftStatus()\n        maxwidth = options.disp_lstatus_max\n        if maxwidth > 0:\n            lstatus = middleTruncate(lstatus, maxwidth//2)\n\n        x = clipdraw(scr, y, 0, lstatus, attr, w=vs.windowWidth-1)\n\n        vd.onMouse(scr, y, 0, 1, x,\n                        BUTTON1_PRESSED='sheets',\n                        BUTTON3_PRESSED='rename-sheet',\n                        BUTTON3_CLICKED='rename-sheet')\n    except Exception as e:\n        vd.exceptionCaught(e)\n\n    if not active:\n        return\n\n    one = False\n    for (pri, msgparts), n in sorted(vd.statuses.items(), key=lambda k: -k[0][0]):\n        try:\n            if x > vs.windowWidth:\n                break\n            if one:  # any messages already:\n                x += clipdraw(scr, y, x, sep, attr, w=vs.windowWidth-x)\n            one = True\n            msg = composeStatus(msgparts, n)\n\n            if pri == 3: msgattr = error_attr\n            elif pri == 2: msgattr = warn_attr\n            elif pri == 1: msgattr = warn_attr\n            else: msgattr = attr\n            x += clipdraw(scr, y, x, msg, msgattr, w=vs.windowWidth-x)\n        except Exception as e:\n            vd.exceptionCaught(e)\n\n\n@VisiData.api\ndef rightStatus(vd, sheet):\n    'Return right side of status bar.  Overrideable.'\n    return options.disp_rstatus_fmt.format(sheet=sheet, vd=vd)\n\n\n@VisiData.api\ndef drawRightStatus(vd, scr, vs):\n    'Draw right side of status bar.  Return length displayed.'\n    rightx = vs.windowWidth\n\n    ret = 0\n    statcolors = [\n        (vd.rightStatus(vs), 'color_status'),\n    ]\n\n    active = vs is vd.activeSheet\n\n    if active:\n        statcolors.append((vd.keystrokes or '', 'color_keystrokes'))\n\n    if vs.currentThreads:\n        statcolors.insert(0, vd.checkMemoryUsage())\n        if vs.progresses:\n            gerund = vs.progresses[0].gerund\n        else:\n            gerund = 'processing'\n        statcolors.insert(1, ('  %s %s\u2026' % (vs.progressPct, gerund), 'color_working'))\n\n    if active and vd.currentReplay:\n        statcolors.insert(0, (vd.replayStatus, 'color_status_replay'))\n\n    for rstatcolor in statcolors:\n        if rstatcolor:\n            try:\n                rstatus, coloropt = rstatcolor\n                rstatus = ' '+rstatus\n                cattr = colors.get_color(coloropt)\n                if scr is vd.winTop:\n                    cattr = update_attr(cattr, colors.color_top_status, 0)\n                if active:\n                    cattr = update_attr(cattr, colors.color_active_status, 0)\n                else:\n                    cattr = update_attr(cattr, colors.color_inactive_status, 0)\n                statuslen = clipdraw(scr, vs.windowHeight-1, rightx, rstatus, cattr.attr, w=vs.windowWidth-1, rtl=True)\n                rightx -= statuslen\n                ret += statuslen\n            except Exception as e:\n                vd.exceptionCaught(e)\n\n    if scr:\n        curses.doupdate()\n    return ret\n\n\nclass StatusSheet(Sheet):\n    precious = False\n    rowtype = 'statuses'  # rowdef: (priority, args, nrepeats)\n    columns = [\n        ColumnItem('priority', 0, type=int, width=0),\n        ColumnItem('nrepeats', 2, type=int, width=0),\n        ColumnItem('args', 1, width=0),\n        Column('message', getter=lambda col,row: composeStatus(row[1], row[2])),\n    ]\n    colorizers = [\n        RowColorizer(1, 'color_error', lambda s,c,r,v: r and r[0] == 3),\n        RowColorizer(1, 'color_warning', lambda s,c,r,v: r and r[0] in [1,2]),\n    ]\n\n    def reload(self):\n        self.rows = self.source\n\n\n@VisiData.property\ndef statusHistorySheet(vd):\n    return StatusSheet(\"status_history\", source=vd.statusHistory[::-1])  # in reverse order\n\n\nBaseSheet.addCommand('^P', 'open-statuses', 'vd.push(vd.statusHistorySheet)', 'open Status History')\n", "path": "visidata/statusbar.py"}], "after_files": [{"content": "import operator\nimport string\n\n'Various helper classes and functions.'\n\n__all__ = ['AlwaysDict', 'AttrDict', 'moveListItem', 'namedlist', 'classproperty', 'MissingAttrFormatter']\n\n\nclass AlwaysDict(dict):\n    'return same val for all keys'\n    def __init__(self, val, **kwargs):\n        super().__init__(**kwargs)\n        self._val = val\n\n    def __getitem__(self, k):\n        return self._val\n\nclass AttrDict(dict):\n    'Augment a dict with more convenient .attr syntax.  not-present keys return None.'\n    def __getattr__(self, k):\n        try:\n            return self[k]\n        except KeyError:\n            if k.startswith(\"__\"):\n                raise AttributeError\n            return None\n\n    def __setattr__(self, k, v):\n        self[k] = v\n\n    def __dir__(self):\n        return self.keys()\n\n\nclass classproperty(property):\n    def __get__(self, cls, obj):\n        return classmethod(self.fget).__get__(None, obj or cls)()\n\n\ndef moveListItem(L, fromidx, toidx):\n    \"Move element within list `L` and return element's new index.\"\n    toidx = min(max(toidx, 0), len(L)-1)\n    fromidx = min(max(fromidx, 0), len(L)-1)\n    r = L.pop(fromidx)\n    L.insert(toidx, r)\n    return toidx\n\n\nclass OnExit:\n    '\"with OnExit(func, ...):\" calls func(...) when the context is exited'\n    def __init__(self, func, *args, **kwargs):\n        self.func = func\n        self.args = args\n        self.kwargs = kwargs\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self, exc_type, exc_value, exc_traceback):\n        try:\n            self.func(*self.args, **self.kwargs)\n        except Exception as e:\n            vd.exceptionCaught(e)\n\n\ndef itemsetter(i):\n    def g(obj, v):\n        obj[i] = v\n    return g\n\n\ndef namedlist(objname, fieldnames):\n    'like namedtuple but editable'\n    class NamedListTemplate(list):\n        __name__ = objname\n        _fields = fieldnames\n\n        def __init__(self, L=None, **kwargs):\n            if L is None:\n                L = [None]*len(self._fields)\n            elif len(L) < len(self._fields):\n                L.extend([None]*(len(self._fields) - len(L)))\n            super().__init__(L)\n            for k, v in kwargs.items():\n                setattr(self, k, v)\n\n        def __getattr__(self, k):\n            'to enable .fieldname'\n            try:\n                return self[self._fields.index(k)]\n            except ValueError:\n                raise AttributeError\n\n        def __setattr__(self, k, v):\n            'to enable .fieldname ='\n            try:\n                self[self._fields.index(k)] = v\n            except ValueError:\n                super().__setattr__(k, v)\n\n    return NamedListTemplate\n\nclass MissingAttrFormatter(string.Formatter):\n    \"formats {} fields with `''`, that would normally result in a raised KeyError or AttributeError; intended for user customisable format strings.\"\n    def get_field(self, field_name, *args, **kwargs):\n        try:\n            return super().get_field(field_name, *args, **kwargs)\n        except (KeyError, AttributeError):\n            return (None, field_name)\n\n    def format_field(self, value, format_spec):\n        # value is missing\n        if not value:\n            return ''\n        return super().format_field(value, format_spec)\n", "path": "visidata/utils.py"}, {"content": "import collections\nimport curses\n\nfrom visidata import vd, VisiData, BaseSheet, Sheet, ColumnItem, Column, RowColorizer, options, colors, wrmap, clipdraw, ExpectedException, update_attr, theme, MissingAttrFormatter\n\n\n__all__ = ['StatusSheet', 'status', 'error', 'fail', 'warning', 'debug']\n\ntheme('disp_rstatus_fmt', ' {sheet.longname} {sheet.nRows:9d} {sheet.rowtype} ', 'right-side status format string')\ntheme('disp_status_fmt', '{sheet.shortcut}\u203a {sheet.name}| ', 'status line prefix')\ntheme('disp_lstatus_max', 0, 'maximum length of left status line')\ntheme('disp_status_sep', ' | ', 'separator between statuses')\n\ntheme('color_keystrokes', 'white', 'color of input keystrokes on status line')\ntheme('color_status', 'bold', 'status line color')\ntheme('color_error', 'red', 'error message color')\ntheme('color_warning', 'yellow', 'warning message color')\ntheme('color_top_status', 'underline', 'top window status bar color')\ntheme('color_active_status', 'bold', ' active window status bar color')\ntheme('color_inactive_status', '8', 'inactive window status bar color')\n\nBaseSheet.init('longname', lambda: '')\n\nvd.beforeExecHooks.append(lambda sheet, cmd, args, ks: setattr(sheet, 'longname', cmd.longname))\n\n@VisiData.lazy_property\ndef statuses(vd):\n    return collections.OrderedDict()  # (priority, statusmsg) -> num_repeats; shown until next action\n\n\n@VisiData.lazy_property\ndef statusHistory(vd):\n    return list()  # list of [priority, statusmsg, repeats] for all status messages ever\n\n\n@VisiData.global_api\ndef status(self, *args, priority=0):\n    'Display *args* on status until next action.'\n    if not args:\n        return True\n\n    k = (priority, tuple(map(str, args)))\n    self.statuses[k] = self.statuses.get(k, 0) + 1\n\n    if self.statusHistory:\n        prevpri, prevargs, prevn = self.statusHistory[-1]\n        if prevpri == priority and prevargs == args:\n            self.statusHistory[-1][2] += 1\n            return True\n\n    self.statusHistory.append([priority, args, 1])\n    return True\n\n@VisiData.global_api\ndef error(vd, *args):\n    'Abort with ExpectedException, and display *args* on status as an error.'\n    vd.status(*args, priority=3)\n    raise ExpectedException(args[0] if args else '')\n\n@VisiData.global_api\ndef fail(vd, *args):\n    'Abort with ExpectedException, and display *args* on status as a warning.'\n    vd.status(*args, priority=2)\n    raise ExpectedException(args[0] if args else '')\n\n@VisiData.global_api\ndef warning(vd, *args):\n    'Display *args* on status as a warning.'\n    vd.status(*args, priority=1)\n\n@VisiData.global_api\ndef debug(vd, *args, **kwargs):\n    'Display *args* on status if options.debug is set.'\n    if options.debug:\n        return vd.status(*args, **kwargs)\n\ndef middleTruncate(s, w):\n    if len(s) <= w:\n        return s\n    return s[:w] + options.disp_truncator + s[-w:]\n\n\ndef composeStatus(msgparts, n):\n    msg = '; '.join(wrmap(str, msgparts))\n    if n > 1:\n        msg = '[%sx] %s' % (n, msg)\n    return msg\n\n\n@BaseSheet.api\ndef leftStatus(sheet):\n    'Return left side of status bar for this sheet. Overridable.'\n    return options.disp_status_fmt.format(sheet=sheet, vd=vd)\n\n\n@VisiData.api\ndef drawLeftStatus(vd, scr, vs):\n    'Draw left side of status bar.'\n    cattr = colors.get_color('color_status')\n    active = (vs is vd.sheets[0]) if vd.sheets else False # active sheet\n    if active:\n        cattr = update_attr(cattr, colors.color_active_status, 0)\n    else:\n        cattr = update_attr(cattr, colors.color_inactive_status, 0)\n\n    if scr is vd.winTop:\n        cattr = update_attr(cattr, colors.color_top_status, 1)\n\n    attr = cattr.attr\n    error_attr = update_attr(cattr, colors.color_error, 1).attr\n    warn_attr = update_attr(cattr, colors.color_warning, 2).attr\n    sep = options.disp_status_sep\n\n    x = 0\n    y = vs.windowHeight-1  # status for each window\n    try:\n        lstatus = vs.leftStatus()\n        maxwidth = options.disp_lstatus_max\n        if maxwidth > 0:\n            lstatus = middleTruncate(lstatus, maxwidth//2)\n\n        x = clipdraw(scr, y, 0, lstatus, attr, w=vs.windowWidth-1)\n\n        vd.onMouse(scr, y, 0, 1, x,\n                        BUTTON1_PRESSED='sheets',\n                        BUTTON3_PRESSED='rename-sheet',\n                        BUTTON3_CLICKED='rename-sheet')\n    except Exception as e:\n        vd.exceptionCaught(e)\n\n    if not active:\n        return\n\n    one = False\n    for (pri, msgparts), n in sorted(vd.statuses.items(), key=lambda k: -k[0][0]):\n        try:\n            if x > vs.windowWidth:\n                break\n            if one:  # any messages already:\n                x += clipdraw(scr, y, x, sep, attr, w=vs.windowWidth-x)\n            one = True\n            msg = composeStatus(msgparts, n)\n\n            if pri == 3: msgattr = error_attr\n            elif pri == 2: msgattr = warn_attr\n            elif pri == 1: msgattr = warn_attr\n            else: msgattr = attr\n            x += clipdraw(scr, y, x, msg, msgattr, w=vs.windowWidth-x)\n        except Exception as e:\n            vd.exceptionCaught(e)\n\n\n@VisiData.api\ndef rightStatus(vd, sheet):\n    'Return right side of status bar.  Overrideable.'\n    return MissingAttrFormatter().format(sheet.options.disp_rstatus_fmt, sheet=sheet, vd=vd)\n\n\n@VisiData.api\ndef drawRightStatus(vd, scr, vs):\n    'Draw right side of status bar.  Return length displayed.'\n    rightx = vs.windowWidth\n\n    ret = 0\n    statcolors = [\n        (vd.rightStatus(vs), 'color_status'),\n    ]\n\n    active = vs is vd.activeSheet\n\n    if active:\n        statcolors.append((vd.keystrokes or '', 'color_keystrokes'))\n\n    if vs.currentThreads:\n        statcolors.insert(0, vd.checkMemoryUsage())\n        if vs.progresses:\n            gerund = vs.progresses[0].gerund\n        else:\n            gerund = 'processing'\n        statcolors.insert(1, ('  %s %s\u2026' % (vs.progressPct, gerund), 'color_working'))\n\n    if active and vd.currentReplay:\n        statcolors.insert(0, (vd.replayStatus, 'color_status_replay'))\n\n    for rstatcolor in statcolors:\n        if rstatcolor:\n            try:\n                rstatus, coloropt = rstatcolor\n                rstatus = ' '+rstatus\n                cattr = colors.get_color(coloropt)\n                if scr is vd.winTop:\n                    cattr = update_attr(cattr, colors.color_top_status, 0)\n                if active:\n                    cattr = update_attr(cattr, colors.color_active_status, 0)\n                else:\n                    cattr = update_attr(cattr, colors.color_inactive_status, 0)\n                statuslen = clipdraw(scr, vs.windowHeight-1, rightx, rstatus, cattr.attr, w=vs.windowWidth-1, rtl=True)\n                rightx -= statuslen\n                ret += statuslen\n            except Exception as e:\n                vd.exceptionCaught(e)\n\n    if scr:\n        curses.doupdate()\n    return ret\n\n\nclass StatusSheet(Sheet):\n    precious = False\n    rowtype = 'statuses'  # rowdef: (priority, args, nrepeats)\n    columns = [\n        ColumnItem('priority', 0, type=int, width=0),\n        ColumnItem('nrepeats', 2, type=int, width=0),\n        ColumnItem('args', 1, width=0),\n        Column('message', getter=lambda col,row: composeStatus(row[1], row[2])),\n    ]\n    colorizers = [\n        RowColorizer(1, 'color_error', lambda s,c,r,v: r and r[0] == 3),\n        RowColorizer(1, 'color_warning', lambda s,c,r,v: r and r[0] in [1,2]),\n    ]\n\n    def reload(self):\n        self.rows = self.source\n\n\n@VisiData.property\ndef statusHistorySheet(vd):\n    return StatusSheet(\"status_history\", source=vd.statusHistory[::-1])  # in reverse order\n\n\nBaseSheet.addCommand('^P', 'open-statuses', 'vd.push(vd.statusHistorySheet)', 'open Status History')\n", "path": "visidata/statusbar.py"}]}