We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Fixed width saver and loader don't round trip. (Columns expand with increasing number of spaces)
**Small description**

Open `test.csv`:

``` csv
colours,counts
red,3
green,5
blue,8
```

Then save it as `test.fixed`:

```
colours   counts   
red       3        
green     5        
blue      8        
```

Each column is separated by three spaces.

Now, open the newly saved `test.fixed`, and save it as `test.fixed.csv`.
Upon inspection, you will see that the three spaces have been included as column data, rather than being discarded as a separator:

```
colours,counts
red       ,3     
green     ,5     
blue      ,8     
```

If you repeat this process, three spaces get appended to each column every time you repeat a round trip.

**Expected result**

I expect to be able to round trip from CSV to Fixed and back without extra spaces being added to the data.


**Steps to reproduce with sample data and a .vd**

[test-fixed-saver.zip](https://github.com/saulpw/visidata/files/13938788/test-fixed-saver.zip)

**Additional context**

- saul.pw/VisiData v3.0.1
- Python 3.10.12


--- 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/loaders/fixed_width.py`
Content:
```
1 
2 from visidata import VisiData, vd, Sheet, Column, Progress, SequenceSheet
3 
4 
5 vd.option('fixed_rows', 1000, 'number of rows to check for fixed width columns')
6 vd.option('fixed_maxcols', 0, 'max number of fixed-width columns to create (0 is no max)')
7 
8 @VisiData.api
9 def open_fixed(vd, p):
10     return FixedWidthColumnsSheet(p.base_stem, source=p, headerlines=[])
11 
12 class FixedWidthColumn(Column):
13     def __init__(self, name, i, j, **kwargs):
14         super().__init__(name, **kwargs)
15         self.i, self.j = i, j
16 
17     def calcValue(self, row):
18         return row[0][self.i:self.j]
19 
20     def putValue(self, row, value):
21         value = str(value)[:self.j-self.i]
22         j = self.j or len(row)
23         row[0] = row[0][:self.i] + '%-*s' % (j-self.i, value) + row[0][self.j:]
24 
25 def columnize(rows):
26     'Generate (i,j) indexes for fixed-width columns found in rows'
27 
28     ## find all character columns that are not spaces ever
29     allNonspaces = set()
30     for r in rows:
31         for i, ch in enumerate(r):
32             if not ch.isspace():
33                 allNonspaces.add(i)
34 
35     colstart = 0
36     prev = 0
37 
38     # collapse fields
39     for i in allNonspaces:
40         if i > prev+1:
41             yield colstart, i
42             colstart = i
43         prev = i
44 
45     yield colstart, prev+1   # final column gets rest of line
46 
47 
48 class FixedWidthColumnsSheet(SequenceSheet):
49     rowtype = 'lines'  # rowdef: [line] (wrapping in list makes it unique and modifiable)
50     def addRow(self, row, index=None):
51         Sheet.addRow(self, row, index=index)
52 
53     def iterload(self):
54         itsource = iter(self.source)
55 
56         # compute fixed width columns from first fixed_rows lines
57         maxcols = self.options.fixed_maxcols
58         self.columns = []
59         fixedRows = list([x] for x in self.optlines(itsource, 'fixed_rows'))
60         for i, j in columnize(list(r[0] for r in fixedRows)):
61             if maxcols and self.nCols >= maxcols-1:
62                 self.addColumn(FixedWidthColumn('', i, None))
63                 break
64             else:
65                 self.addColumn(FixedWidthColumn('', i, j))
66 
67         yield from fixedRows
68 
69         self.setColNames(self.headerlines)
70 
71         yield from ([line] for line in itsource)
72 
73     def setCols(self, headerlines):
74         self.headerlines = headerlines
75 
76 
77 @VisiData.api
78 def save_fixed(vd, p, *vsheets):
79     with p.open(mode='w', encoding=vsheets[0].options.save_encoding) as fp:
80         for sheet in vsheets:
81             if len(vsheets) > 1:
82                 fp.write('%s\n\n' % sheet.name)
83 
84             widths = {}  # Column -> width:int
85             # headers
86             for col in Progress(sheet.visibleCols, gerund='sizing'):
87                 widths[col] = col.getMaxWidth(sheet.rows)  #1849
88                 fp.write(('{0:%s} ' % widths[col]).format(col.name))
89             fp.write('\n')
90 
91             # rows
92             with Progress(gerund='saving'):
93                 for dispvals in sheet.iterdispvals(format=True):
94                     for col, val in dispvals.items():
95                         fp.write(('{0:%s%s.%s} ' % ('>' if vd.isNumeric(col) else '<', widths[col], widths[col])).format(val))
96                     fp.write('\n')
97
```
--- 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/loaders/fixed_width.py b/visidata/loaders/fixed_width.py
--- a/visidata/loaders/fixed_width.py
+++ b/visidata/loaders/fixed_width.py
@@ -1,5 +1,5 @@
 
-from visidata import VisiData, vd, Sheet, Column, Progress, SequenceSheet
+from visidata import VisiData, vd, Sheet, Column, Progress, SequenceSheet, Column, dispwidth
 
 
 vd.option('fixed_rows', 1000, 'number of rows to check for fixed width columns')
@@ -9,6 +9,22 @@
 def open_fixed(vd, p):
     return FixedWidthColumnsSheet(p.base_stem, source=p, headerlines=[])
 
+@Column.api
+def getMaxDataWidth(col, rows):  #2255 need real max width for fixed width saver
+    '''Return the maximum length of any cell in column or its header,
+    even if wider than window. (Slow for large cells!)'''
+
+    w = 0
+    nlen = dispwidth(col.name)
+    if len(rows) > 0:
+        w_max = 0
+        for r in rows:
+            row_w = dispwidth(col.getDisplayValue(r))
+            if w_max < row_w:
+                w_max = row_w
+        w = w_max
+    return max(w, nlen)
+
 class FixedWidthColumn(Column):
     def __init__(self, name, i, j, **kwargs):
         super().__init__(name, **kwargs)
@@ -38,7 +54,7 @@
     # collapse fields
     for i in allNonspaces:
         if i > prev+1:
-            yield colstart, i
+            yield colstart, prev+1 #2255
             colstart = i
         prev = i
 
@@ -84,7 +100,7 @@
             widths = {}  # Column -> width:int
             # headers
             for col in Progress(sheet.visibleCols, gerund='sizing'):
-                widths[col] = col.getMaxWidth(sheet.rows)  #1849
+                widths[col] = col.getMaxDataWidth(sheet.rows)  #1849 #2255
                 fp.write(('{0:%s} ' % widths[col]).format(col.name))
             fp.write('\n')

{"golden_diff": "diff --git a/visidata/loaders/fixed_width.py b/visidata/loaders/fixed_width.py\n--- a/visidata/loaders/fixed_width.py\n+++ b/visidata/loaders/fixed_width.py\n@@ -1,5 +1,5 @@\n \n-from visidata import VisiData, vd, Sheet, Column, Progress, SequenceSheet\n+from visidata import VisiData, vd, Sheet, Column, Progress, SequenceSheet, Column, dispwidth\n \n \n vd.option('fixed_rows', 1000, 'number of rows to check for fixed width columns')\n@@ -9,6 +9,22 @@\n def open_fixed(vd, p):\n     return FixedWidthColumnsSheet(p.base_stem, source=p, headerlines=[])\n \n+@Column.api\n+def getMaxDataWidth(col, rows):  #2255 need real max width for fixed width saver\n+    '''Return the maximum length of any cell in column or its header,\n+    even if wider than window. (Slow for large cells!)'''\n+\n+    w = 0\n+    nlen = dispwidth(col.name)\n+    if len(rows) > 0:\n+        w_max = 0\n+        for r in rows:\n+            row_w = dispwidth(col.getDisplayValue(r))\n+            if w_max < row_w:\n+                w_max = row_w\n+        w = w_max\n+    return max(w, nlen)\n+\n class FixedWidthColumn(Column):\n     def __init__(self, name, i, j, **kwargs):\n         super().__init__(name, **kwargs)\n@@ -38,7 +54,7 @@\n     # collapse fields\n     for i in allNonspaces:\n         if i > prev+1:\n-            yield colstart, i\n+            yield colstart, prev+1 #2255\n             colstart = i\n         prev = i\n \n@@ -84,7 +100,7 @@\n             widths = {}  # Column -> width:int\n             # headers\n             for col in Progress(sheet.visibleCols, gerund='sizing'):\n-                widths[col] = col.getMaxWidth(sheet.rows)  #1849\n+                widths[col] = col.getMaxDataWidth(sheet.rows)  #1849 #2255\n                 fp.write(('{0:%s} ' % widths[col]).format(col.name))\n             fp.write('\\n')\n", "issue": "Fixed width saver and loader don't round trip. (Columns expand with increasing number of spaces)\n**Small description**\r\n\r\nOpen `test.csv`:\r\n\r\n``` csv\r\ncolours,counts\r\nred,3\r\ngreen,5\r\nblue,8\r\n```\r\n\r\nThen save it as `test.fixed`:\r\n\r\n```\r\ncolours   counts   \r\nred       3        \r\ngreen     5        \r\nblue      8        \r\n```\r\n\r\nEach column is separated by three spaces.\r\n\r\nNow, open the newly saved `test.fixed`, and save it as `test.fixed.csv`.\r\nUpon inspection, you will see that the three spaces have been included as column data, rather than being discarded as a separator:\r\n\r\n```\r\ncolours,counts\r\nred       ,3     \r\ngreen     ,5     \r\nblue      ,8     \r\n```\r\n\r\nIf you repeat this process, three spaces get appended to each column every time you repeat a round trip.\r\n\r\n**Expected result**\r\n\r\nI expect to be able to round trip from CSV to Fixed and back without extra spaces being added to the data.\r\n\r\n\r\n**Steps to reproduce with sample data and a .vd**\r\n\r\n[test-fixed-saver.zip](https://github.com/saulpw/visidata/files/13938788/test-fixed-saver.zip)\r\n\r\n**Additional context**\r\n\r\n- saul.pw/VisiData v3.0.1\r\n- Python 3.10.12\r\n\n", "before_files": [{"content": "\nfrom visidata import VisiData, vd, Sheet, Column, Progress, SequenceSheet\n\n\nvd.option('fixed_rows', 1000, 'number of rows to check for fixed width columns')\nvd.option('fixed_maxcols', 0, 'max number of fixed-width columns to create (0 is no max)')\n\n@VisiData.api\ndef open_fixed(vd, p):\n    return FixedWidthColumnsSheet(p.base_stem, source=p, headerlines=[])\n\nclass FixedWidthColumn(Column):\n    def __init__(self, name, i, j, **kwargs):\n        super().__init__(name, **kwargs)\n        self.i, self.j = i, j\n\n    def calcValue(self, row):\n        return row[0][self.i:self.j]\n\n    def putValue(self, row, value):\n        value = str(value)[:self.j-self.i]\n        j = self.j or len(row)\n        row[0] = row[0][:self.i] + '%-*s' % (j-self.i, value) + row[0][self.j:]\n\ndef columnize(rows):\n    'Generate (i,j) indexes for fixed-width columns found in rows'\n\n    ## find all character columns that are not spaces ever\n    allNonspaces = set()\n    for r in rows:\n        for i, ch in enumerate(r):\n            if not ch.isspace():\n                allNonspaces.add(i)\n\n    colstart = 0\n    prev = 0\n\n    # collapse fields\n    for i in allNonspaces:\n        if i > prev+1:\n            yield colstart, i\n            colstart = i\n        prev = i\n\n    yield colstart, prev+1   # final column gets rest of line\n\n\nclass FixedWidthColumnsSheet(SequenceSheet):\n    rowtype = 'lines'  # rowdef: [line] (wrapping in list makes it unique and modifiable)\n    def addRow(self, row, index=None):\n        Sheet.addRow(self, row, index=index)\n\n    def iterload(self):\n        itsource = iter(self.source)\n\n        # compute fixed width columns from first fixed_rows lines\n        maxcols = self.options.fixed_maxcols\n        self.columns = []\n        fixedRows = list([x] for x in self.optlines(itsource, 'fixed_rows'))\n        for i, j in columnize(list(r[0] for r in fixedRows)):\n            if maxcols and self.nCols >= maxcols-1:\n                self.addColumn(FixedWidthColumn('', i, None))\n                break\n            else:\n                self.addColumn(FixedWidthColumn('', i, j))\n\n        yield from fixedRows\n\n        self.setColNames(self.headerlines)\n\n        yield from ([line] for line in itsource)\n\n    def setCols(self, headerlines):\n        self.headerlines = headerlines\n\n\n@VisiData.api\ndef save_fixed(vd, p, *vsheets):\n    with p.open(mode='w', encoding=vsheets[0].options.save_encoding) as fp:\n        for sheet in vsheets:\n            if len(vsheets) > 1:\n                fp.write('%s\\n\\n' % sheet.name)\n\n            widths = {}  # Column -> width:int\n            # headers\n            for col in Progress(sheet.visibleCols, gerund='sizing'):\n                widths[col] = col.getMaxWidth(sheet.rows)  #1849\n                fp.write(('{0:%s} ' % widths[col]).format(col.name))\n            fp.write('\\n')\n\n            # rows\n            with Progress(gerund='saving'):\n                for dispvals in sheet.iterdispvals(format=True):\n                    for col, val in dispvals.items():\n                        fp.write(('{0:%s%s.%s} ' % ('>' if vd.isNumeric(col) else '<', widths[col], widths[col])).format(val))\n                    fp.write('\\n')\n", "path": "visidata/loaders/fixed_width.py"}], "after_files": [{"content": "\nfrom visidata import VisiData, vd, Sheet, Column, Progress, SequenceSheet, Column, dispwidth\n\n\nvd.option('fixed_rows', 1000, 'number of rows to check for fixed width columns')\nvd.option('fixed_maxcols', 0, 'max number of fixed-width columns to create (0 is no max)')\n\n@VisiData.api\ndef open_fixed(vd, p):\n    return FixedWidthColumnsSheet(p.base_stem, source=p, headerlines=[])\n\n@Column.api\ndef getMaxDataWidth(col, rows):  #2255 need real max width for fixed width saver\n    '''Return the maximum length of any cell in column or its header,\n    even if wider than window. (Slow for large cells!)'''\n\n    w = 0\n    nlen = dispwidth(col.name)\n    if len(rows) > 0:\n        w_max = 0\n        for r in rows:\n            row_w = dispwidth(col.getDisplayValue(r))\n            if w_max < row_w:\n                w_max = row_w\n        w = w_max\n    return max(w, nlen)\n\nclass FixedWidthColumn(Column):\n    def __init__(self, name, i, j, **kwargs):\n        super().__init__(name, **kwargs)\n        self.i, self.j = i, j\n\n    def calcValue(self, row):\n        return row[0][self.i:self.j]\n\n    def putValue(self, row, value):\n        value = str(value)[:self.j-self.i]\n        j = self.j or len(row)\n        row[0] = row[0][:self.i] + '%-*s' % (j-self.i, value) + row[0][self.j:]\n\ndef columnize(rows):\n    'Generate (i,j) indexes for fixed-width columns found in rows'\n\n    ## find all character columns that are not spaces ever\n    allNonspaces = set()\n    for r in rows:\n        for i, ch in enumerate(r):\n            if not ch.isspace():\n                allNonspaces.add(i)\n\n    colstart = 0\n    prev = 0\n\n    # collapse fields\n    for i in allNonspaces:\n        if i > prev+1:\n            yield colstart, prev+1 #2255\n            colstart = i\n        prev = i\n\n    yield colstart, prev+1   # final column gets rest of line\n\n\nclass FixedWidthColumnsSheet(SequenceSheet):\n    rowtype = 'lines'  # rowdef: [line] (wrapping in list makes it unique and modifiable)\n    def addRow(self, row, index=None):\n        Sheet.addRow(self, row, index=index)\n\n    def iterload(self):\n        itsource = iter(self.source)\n\n        # compute fixed width columns from first fixed_rows lines\n        maxcols = self.options.fixed_maxcols\n        self.columns = []\n        fixedRows = list([x] for x in self.optlines(itsource, 'fixed_rows'))\n        for i, j in columnize(list(r[0] for r in fixedRows)):\n            if maxcols and self.nCols >= maxcols-1:\n                self.addColumn(FixedWidthColumn('', i, None))\n                break\n            else:\n                self.addColumn(FixedWidthColumn('', i, j))\n\n        yield from fixedRows\n\n        self.setColNames(self.headerlines)\n\n        yield from ([line] for line in itsource)\n\n    def setCols(self, headerlines):\n        self.headerlines = headerlines\n\n\n@VisiData.api\ndef save_fixed(vd, p, *vsheets):\n    with p.open(mode='w', encoding=vsheets[0].options.save_encoding) as fp:\n        for sheet in vsheets:\n            if len(vsheets) > 1:\n                fp.write('%s\\n\\n' % sheet.name)\n\n            widths = {}  # Column -> width:int\n            # headers\n            for col in Progress(sheet.visibleCols, gerund='sizing'):\n                widths[col] = col.getMaxDataWidth(sheet.rows)  #1849 #2255\n                fp.write(('{0:%s} ' % widths[col]).format(col.name))\n            fp.write('\\n')\n\n            # rows\n            with Progress(gerund='saving'):\n                for dispvals in sheet.iterdispvals(format=True):\n                    for col, val in dispvals.items():\n                        fp.write(('{0:%s%s.%s} ' % ('>' if vd.isNumeric(col) else '<', widths[col], widths[col])).format(val))\n                    fp.write('\\n')\n", "path": "visidata/loaders/fixed_width.py"}]}