We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[v2.9dev] Disable adding new row in DirSheet
**Small description**
Unless used, `add-row` should probably be disabled on DirSheet as it creates an error

**Expected result**
A warning to be shown to the user that a new row/file cannot be created.

**Actual result with screenshot**
![image](https://user-images.githubusercontent.com/11225502/155373843-aeef6d8e-cc96-4559-b3e6-dc9690aab25f.png)

**Steps to reproduce with sample data and a .vd**
Open vd directory with vd (`vd .`) and press `a`

--- 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/shell.py`
Content:
```
1 import os
2 import shutil
3 import stat
4 import subprocess
5 import contextlib
6 try:
7     import pwd
8     import grp
9 except ImportError:
10     pass # pwd,grp modules not available on Windows
11 
12 from visidata import Column, Sheet, LazyComputeRow, asynccache, BaseSheet, vd
13 from visidata import Path, ENTER, date, asyncthread, FileExistsError, VisiData
14 from visidata import modtime, filesize, vstat, Progress, TextSheet
15 
16 
17 vd.option('dir_recurse', False, 'walk source path recursively on DirSheet')
18 vd.option('dir_hidden', False, 'load hidden files on DirSheet')
19 
20 
21 @VisiData.lazy_property
22 def currentDirSheet(p):
23     'Support opening the current DirSheet from the vdmenu'
24     return DirSheet('.', source=Path('.'))
25 
26 @asyncthread
27 def exec_shell(*args):
28     p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
29     out, err = p.communicate()
30     if err or out:
31         lines = err.decode('utf8').splitlines() + out.decode('utf8').splitlines()
32         vd.push(TextSheet(' '.join(args), source=lines))
33 
34 
35 @VisiData.api
36 def open_dir(vd, p):
37     return DirSheet(p.name, source=p)
38 
39 @VisiData.api
40 def open_fdir(vd, p):
41     return FileListSheet(p.name, source=p)
42 
43 @VisiData.api
44 def addShellColumns(vd, cmd, sheet):
45     shellcol = ColumnShell(cmd, source=sheet, width=0)
46     sheet.addColumnAtCursor(
47             shellcol,
48             Column(cmd+'_stdout', srccol=shellcol, getter=lambda col,row: col.srccol.getValue(row)[0]),
49             Column(cmd+'_stderr', srccol=shellcol, getter=lambda col,row: col.srccol.getValue(row)[1]))
50 
51 
52 class ColumnShell(Column):
53     def __init__(self, name, cmd=None, **kwargs):
54         super().__init__(name, **kwargs)
55         self.expr = cmd or name
56 
57     @asynccache(lambda col,row: (col, col.sheet.rowid(row)))
58     def calcValue(self, row):
59         try:
60             import shlex
61             args = []
62             context = LazyComputeRow(self.source, row)
63             for arg in shlex.split(self.expr):
64                 if arg.startswith('$'):
65                     args.append(shlex.quote(str(context[arg[1:]])))
66                 else:
67                     args.append(arg)
68 
69             p = subprocess.Popen([os.getenv('SHELL', 'bash'), '-c', ' '.join(args)],
70                     stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
71             return p.communicate()
72         except Exception as e:
73             vd.exceptionCaught(e)
74 
75 
76 class DirSheet(Sheet):
77     'Sheet displaying directory, using ENTER to open a particular file.  Edited fields are applied to the filesystem.'
78     rowtype = 'files' # rowdef: Path
79     defer = True
80     columns = [
81         Column('directory',
82             getter=lambda col,row: str(row.parent) if str(row.parent) == '.' else str(row.parent) + '/',
83             setter=lambda col,row,val: col.sheet.moveFile(row, val)),
84         Column('filename',
85             getter=lambda col,row: row.name + row.suffix,
86             setter=lambda col,row,val: col.sheet.renameFile(row, val)),
87         Column('abspath', width=0, type=str,
88             getter=lambda col,row: row,
89             setter=lambda col,row,val: os.rename(row, val)),
90         Column('ext', getter=lambda col,row: row.is_dir() and '/' or row.ext),
91         Column('size', type=int,
92             getter=lambda col,row: filesize(row),
93             setter=lambda col,row,val: os.truncate(row, int(val))),
94         Column('modtime', type=date,
95             getter=lambda col,row: modtime(row),
96             setter=lambda col,row,val: os.utime(row, times=((row.stat().st_atime, float(val))))),
97         Column('owner', width=0,
98             getter=lambda col,row: pwd.getpwuid(row.stat().st_uid).pw_name,
99             setter=lambda col,row,val: os.chown(row, pwd.getpwnam(val).pw_uid, -1)),
100         Column('group', width=0,
101             getter=lambda col,row: grp.getgrgid(row.stat().st_gid).gr_name,
102             setter=lambda col,row,val: os.chown(row, -1, grp.getgrnam(val).pw_gid)),
103         Column('mode', width=0,
104             getter=lambda col,row: '{:o}'.format(row.stat().st_mode),
105             setter=lambda col,row,val: os.chmod(row, int(val, 8))),
106         Column('filetype', width=0, cache='async', getter=lambda col,row: subprocess.Popen(['file', '--brief', row], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0].strip()),
107     ]
108     nKeys = 2
109     _ordering = [('modtime', True)]  # sort by reverse modtime initially
110 
111     @staticmethod
112     def colorOwner(sheet, col, row, val):
113         ret = ''
114         if col.name == 'group':
115             mode = row.stat().st_mode
116             if mode & stat.S_IXGRP: ret = 'bold '
117             if mode & stat.S_IWGRP: return ret + 'green'
118             if mode & stat.S_IRGRP: return ret + 'yellow'
119         elif col.name == 'owner':
120             mode = row.stat().st_mode
121             if mode & stat.S_IXUSR: ret = 'bold '
122             if mode & stat.S_IWUSR: return ret + 'green'
123             if mode & stat.S_IRUSR: return ret + 'yellow'
124 
125     def moveFile(self, row, newparent):
126         parent = Path(newparent)
127         newpath = Path(parent/(row.name + row.suffix))
128         if parent.exists():
129             if not parent.is_dir():
130                 vd.error('destination %s not a directory' % parent)
131         else:
132             with contextlib.suppress(FileExistsError):
133                 os.makedirs(parent)
134 
135         row.rename(newpath)
136         row.given = newpath # modify visidata.Path
137         self.restat()
138 
139     def renameFile(self, row, val):
140         newpath = row.with_name(val)
141         row.rename(newpath)
142         row.given = newpath
143         self.restat()
144 
145     def removeFile(self, path):
146         if path.is_dir():
147             os.rmdir(path)
148         else:
149             path.unlink()
150 
151     def deleteSourceRow(self, r):
152         self.removeFile(r)
153 
154     def iterload(self):
155         hidden_files = self.options.dir_hidden
156 
157         def _walkfiles(p):
158             basepath = str(p)
159             for folder, subdirs, files in os.walk(basepath):
160                 subfolder = folder[len(basepath)+1:]
161                 if not hidden_files and subfolder.startswith('.'): continue
162                 if subfolder in ['.', '..']: continue
163 
164                 fpath = Path(folder)
165                 yield fpath
166 
167                 for fn in files:
168                     yield fpath/fn
169 
170         def _listfiles(p):
171             basepath = str(p)
172             for fn in os.listdir(basepath):
173                 yield p/fn
174 
175 
176         basepath = str(self.source)
177 
178         folders = set()
179         f = _walkfiles if self.options.dir_recurse else _listfiles
180 
181         for p in f(self.source):
182             if not hidden_files and p.name.startswith('.'):
183                 continue
184 
185             yield p
186 
187     def preloadHook(self):
188         super().preloadHook()
189         Path.stat.cache_clear()
190 
191     def restat(self):
192         vstat.cache_clear()
193 
194     @asyncthread
195     def putChanges(self):
196         self.commitAdds()
197         self.commitMods()
198         self.commitDeletes()
199 
200         self._deferredDels.clear()
201         self.reload()
202 
203 
204 class FileListSheet(DirSheet):
205     _ordering = []
206     def iterload(self):
207         for fn in self.source.open_text():
208             yield Path(fn.rstrip())
209 
210 
211 @VisiData.api
212 def inputShell(vd):
213     cmd = vd.input("sh$ ", type="sh")
214     if '$' not in cmd:
215         vd.warning('no $column in command')
216     return cmd
217 
218 BaseSheet.addCommand('', 'open-dir-current', 'vd.push(vd.currentDirSheet)', 'open Directory Sheet: browse properties of files in current directory')
219 
220 Sheet.addCommand('z;', 'addcol-sh', 'cmd=inputShell(); addShellColumns(cmd, sheet)', 'create new column from bash expression, with $columnNames as variables')
221 
222 DirSheet.addCommand(ENTER, 'open-row', 'vd.push(openSource(cursorRow or fail("no row"), filetype="dir" if cursorRow.is_dir() else LazyComputeRow(sheet, cursorRow).ext))', 'open current file as a new sheet')
223 DirSheet.addCommand('g'+ENTER, 'open-rows', 'for r in selectedRows: vd.push(openSource(r))', 'open selected files as new sheets')
224 DirSheet.addCommand('^O', 'sysopen-row', 'launchEditor(cursorRow)', 'open current file in external $EDITOR')
225 DirSheet.addCommand('g^O', 'sysopen-rows', 'launchEditor(*selectedRows)', 'open selected files in external $EDITOR')
226 
227 DirSheet.addCommand('y', 'copy-row', 'copy_files([cursorRow], inputPath("copy to dest: "))', 'copy file to given directory')
228 DirSheet.addCommand('gy', 'copy-selected', 'copy_files(selectedRows, inputPath("copy to dest: ", value=cursorRow.given))', 'copy selected files to given directory')
229 
230 @DirSheet.api
231 @asyncthread
232 def copy_files(sheet, paths, dest):
233     destdir = Path(dest)
234     destdir.is_dir() or vd.fail('target must be directory')
235     vd.status('copying %s %s to %s' % (len(paths), sheet.rowtype, destdir))
236     os.makedirs(destdir, exist_ok=True)
237     for srcpath in Progress(paths, gerund='copying'):
238         try:
239             destpath = destdir/str(srcpath._path.name)
240             if srcpath.is_dir():
241                 shutil.copy_tree(srcpath, destpath)
242             else:
243                 shutil.copyfile(srcpath, destpath)
244         except Exception as e:
245             vd.exceptionCaught(e)
246 
247 
248 vd.addGlobals({
249     'DirSheet': DirSheet
250 })
251
```
--- 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/shell.py b/visidata/shell.py
--- a/visidata/shell.py
+++ b/visidata/shell.py
@@ -151,6 +151,9 @@
     def deleteSourceRow(self, r):
         self.removeFile(r)
 
+    def newRow(self):
+        vd.fail('new file not supported')
+
     def iterload(self):
         hidden_files = self.options.dir_hidden

{"golden_diff": "diff --git a/visidata/shell.py b/visidata/shell.py\n--- a/visidata/shell.py\n+++ b/visidata/shell.py\n@@ -151,6 +151,9 @@\n     def deleteSourceRow(self, r):\n         self.removeFile(r)\n \n+    def newRow(self):\n+        vd.fail('new file not supported')\n+\n     def iterload(self):\n         hidden_files = self.options.dir_hidden\n", "issue": "[v2.9dev] Disable adding new row in DirSheet\n**Small description**\r\nUnless used, `add-row` should probably be disabled on DirSheet as it creates an error\r\n\r\n**Expected result**\r\nA warning to be shown to the user that a new row/file cannot be created.\r\n\r\n**Actual result with screenshot**\r\n![image](https://user-images.githubusercontent.com/11225502/155373843-aeef6d8e-cc96-4559-b3e6-dc9690aab25f.png)\r\n\r\n**Steps to reproduce with sample data and a .vd**\r\nOpen vd directory with vd (`vd .`) and press `a`\n", "before_files": [{"content": "import os\nimport shutil\nimport stat\nimport subprocess\nimport contextlib\ntry:\n    import pwd\n    import grp\nexcept ImportError:\n    pass # pwd,grp modules not available on Windows\n\nfrom visidata import Column, Sheet, LazyComputeRow, asynccache, BaseSheet, vd\nfrom visidata import Path, ENTER, date, asyncthread, FileExistsError, VisiData\nfrom visidata import modtime, filesize, vstat, Progress, TextSheet\n\n\nvd.option('dir_recurse', False, 'walk source path recursively on DirSheet')\nvd.option('dir_hidden', False, 'load hidden files on DirSheet')\n\n\n@VisiData.lazy_property\ndef currentDirSheet(p):\n    'Support opening the current DirSheet from the vdmenu'\n    return DirSheet('.', source=Path('.'))\n\n@asyncthread\ndef exec_shell(*args):\n    p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n    out, err = p.communicate()\n    if err or out:\n        lines = err.decode('utf8').splitlines() + out.decode('utf8').splitlines()\n        vd.push(TextSheet(' '.join(args), source=lines))\n\n\n@VisiData.api\ndef open_dir(vd, p):\n    return DirSheet(p.name, source=p)\n\n@VisiData.api\ndef open_fdir(vd, p):\n    return FileListSheet(p.name, source=p)\n\n@VisiData.api\ndef addShellColumns(vd, cmd, sheet):\n    shellcol = ColumnShell(cmd, source=sheet, width=0)\n    sheet.addColumnAtCursor(\n            shellcol,\n            Column(cmd+'_stdout', srccol=shellcol, getter=lambda col,row: col.srccol.getValue(row)[0]),\n            Column(cmd+'_stderr', srccol=shellcol, getter=lambda col,row: col.srccol.getValue(row)[1]))\n\n\nclass ColumnShell(Column):\n    def __init__(self, name, cmd=None, **kwargs):\n        super().__init__(name, **kwargs)\n        self.expr = cmd or name\n\n    @asynccache(lambda col,row: (col, col.sheet.rowid(row)))\n    def calcValue(self, row):\n        try:\n            import shlex\n            args = []\n            context = LazyComputeRow(self.source, row)\n            for arg in shlex.split(self.expr):\n                if arg.startswith('$'):\n                    args.append(shlex.quote(str(context[arg[1:]])))\n                else:\n                    args.append(arg)\n\n            p = subprocess.Popen([os.getenv('SHELL', 'bash'), '-c', ' '.join(args)],\n                    stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n            return p.communicate()\n        except Exception as e:\n            vd.exceptionCaught(e)\n\n\nclass DirSheet(Sheet):\n    'Sheet displaying directory, using ENTER to open a particular file.  Edited fields are applied to the filesystem.'\n    rowtype = 'files' # rowdef: Path\n    defer = True\n    columns = [\n        Column('directory',\n            getter=lambda col,row: str(row.parent) if str(row.parent) == '.' else str(row.parent) + '/',\n            setter=lambda col,row,val: col.sheet.moveFile(row, val)),\n        Column('filename',\n            getter=lambda col,row: row.name + row.suffix,\n            setter=lambda col,row,val: col.sheet.renameFile(row, val)),\n        Column('abspath', width=0, type=str,\n            getter=lambda col,row: row,\n            setter=lambda col,row,val: os.rename(row, val)),\n        Column('ext', getter=lambda col,row: row.is_dir() and '/' or row.ext),\n        Column('size', type=int,\n            getter=lambda col,row: filesize(row),\n            setter=lambda col,row,val: os.truncate(row, int(val))),\n        Column('modtime', type=date,\n            getter=lambda col,row: modtime(row),\n            setter=lambda col,row,val: os.utime(row, times=((row.stat().st_atime, float(val))))),\n        Column('owner', width=0,\n            getter=lambda col,row: pwd.getpwuid(row.stat().st_uid).pw_name,\n            setter=lambda col,row,val: os.chown(row, pwd.getpwnam(val).pw_uid, -1)),\n        Column('group', width=0,\n            getter=lambda col,row: grp.getgrgid(row.stat().st_gid).gr_name,\n            setter=lambda col,row,val: os.chown(row, -1, grp.getgrnam(val).pw_gid)),\n        Column('mode', width=0,\n            getter=lambda col,row: '{:o}'.format(row.stat().st_mode),\n            setter=lambda col,row,val: os.chmod(row, int(val, 8))),\n        Column('filetype', width=0, cache='async', getter=lambda col,row: subprocess.Popen(['file', '--brief', row], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0].strip()),\n    ]\n    nKeys = 2\n    _ordering = [('modtime', True)]  # sort by reverse modtime initially\n\n    @staticmethod\n    def colorOwner(sheet, col, row, val):\n        ret = ''\n        if col.name == 'group':\n            mode = row.stat().st_mode\n            if mode & stat.S_IXGRP: ret = 'bold '\n            if mode & stat.S_IWGRP: return ret + 'green'\n            if mode & stat.S_IRGRP: return ret + 'yellow'\n        elif col.name == 'owner':\n            mode = row.stat().st_mode\n            if mode & stat.S_IXUSR: ret = 'bold '\n            if mode & stat.S_IWUSR: return ret + 'green'\n            if mode & stat.S_IRUSR: return ret + 'yellow'\n\n    def moveFile(self, row, newparent):\n        parent = Path(newparent)\n        newpath = Path(parent/(row.name + row.suffix))\n        if parent.exists():\n            if not parent.is_dir():\n                vd.error('destination %s not a directory' % parent)\n        else:\n            with contextlib.suppress(FileExistsError):\n                os.makedirs(parent)\n\n        row.rename(newpath)\n        row.given = newpath # modify visidata.Path\n        self.restat()\n\n    def renameFile(self, row, val):\n        newpath = row.with_name(val)\n        row.rename(newpath)\n        row.given = newpath\n        self.restat()\n\n    def removeFile(self, path):\n        if path.is_dir():\n            os.rmdir(path)\n        else:\n            path.unlink()\n\n    def deleteSourceRow(self, r):\n        self.removeFile(r)\n\n    def iterload(self):\n        hidden_files = self.options.dir_hidden\n\n        def _walkfiles(p):\n            basepath = str(p)\n            for folder, subdirs, files in os.walk(basepath):\n                subfolder = folder[len(basepath)+1:]\n                if not hidden_files and subfolder.startswith('.'): continue\n                if subfolder in ['.', '..']: continue\n\n                fpath = Path(folder)\n                yield fpath\n\n                for fn in files:\n                    yield fpath/fn\n\n        def _listfiles(p):\n            basepath = str(p)\n            for fn in os.listdir(basepath):\n                yield p/fn\n\n\n        basepath = str(self.source)\n\n        folders = set()\n        f = _walkfiles if self.options.dir_recurse else _listfiles\n\n        for p in f(self.source):\n            if not hidden_files and p.name.startswith('.'):\n                continue\n\n            yield p\n\n    def preloadHook(self):\n        super().preloadHook()\n        Path.stat.cache_clear()\n\n    def restat(self):\n        vstat.cache_clear()\n\n    @asyncthread\n    def putChanges(self):\n        self.commitAdds()\n        self.commitMods()\n        self.commitDeletes()\n\n        self._deferredDels.clear()\n        self.reload()\n\n\nclass FileListSheet(DirSheet):\n    _ordering = []\n    def iterload(self):\n        for fn in self.source.open_text():\n            yield Path(fn.rstrip())\n\n\n@VisiData.api\ndef inputShell(vd):\n    cmd = vd.input(\"sh$ \", type=\"sh\")\n    if '$' not in cmd:\n        vd.warning('no $column in command')\n    return cmd\n\nBaseSheet.addCommand('', 'open-dir-current', 'vd.push(vd.currentDirSheet)', 'open Directory Sheet: browse properties of files in current directory')\n\nSheet.addCommand('z;', 'addcol-sh', 'cmd=inputShell(); addShellColumns(cmd, sheet)', 'create new column from bash expression, with $columnNames as variables')\n\nDirSheet.addCommand(ENTER, 'open-row', 'vd.push(openSource(cursorRow or fail(\"no row\"), filetype=\"dir\" if cursorRow.is_dir() else LazyComputeRow(sheet, cursorRow).ext))', 'open current file as a new sheet')\nDirSheet.addCommand('g'+ENTER, 'open-rows', 'for r in selectedRows: vd.push(openSource(r))', 'open selected files as new sheets')\nDirSheet.addCommand('^O', 'sysopen-row', 'launchEditor(cursorRow)', 'open current file in external $EDITOR')\nDirSheet.addCommand('g^O', 'sysopen-rows', 'launchEditor(*selectedRows)', 'open selected files in external $EDITOR')\n\nDirSheet.addCommand('y', 'copy-row', 'copy_files([cursorRow], inputPath(\"copy to dest: \"))', 'copy file to given directory')\nDirSheet.addCommand('gy', 'copy-selected', 'copy_files(selectedRows, inputPath(\"copy to dest: \", value=cursorRow.given))', 'copy selected files to given directory')\n\n@DirSheet.api\n@asyncthread\ndef copy_files(sheet, paths, dest):\n    destdir = Path(dest)\n    destdir.is_dir() or vd.fail('target must be directory')\n    vd.status('copying %s %s to %s' % (len(paths), sheet.rowtype, destdir))\n    os.makedirs(destdir, exist_ok=True)\n    for srcpath in Progress(paths, gerund='copying'):\n        try:\n            destpath = destdir/str(srcpath._path.name)\n            if srcpath.is_dir():\n                shutil.copy_tree(srcpath, destpath)\n            else:\n                shutil.copyfile(srcpath, destpath)\n        except Exception as e:\n            vd.exceptionCaught(e)\n\n\nvd.addGlobals({\n    'DirSheet': DirSheet\n})\n", "path": "visidata/shell.py"}], "after_files": [{"content": "import os\nimport shutil\nimport stat\nimport subprocess\nimport contextlib\ntry:\n    import pwd\n    import grp\nexcept ImportError:\n    pass # pwd,grp modules not available on Windows\n\nfrom visidata import Column, Sheet, LazyComputeRow, asynccache, BaseSheet, vd\nfrom visidata import Path, ENTER, date, asyncthread, FileExistsError, VisiData\nfrom visidata import modtime, filesize, vstat, Progress, TextSheet\n\n\nvd.option('dir_recurse', False, 'walk source path recursively on DirSheet')\nvd.option('dir_hidden', False, 'load hidden files on DirSheet')\n\n\n@VisiData.lazy_property\ndef currentDirSheet(p):\n    'Support opening the current DirSheet from the vdmenu'\n    return DirSheet('.', source=Path('.'))\n\n@asyncthread\ndef exec_shell(*args):\n    p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n    out, err = p.communicate()\n    if err or out:\n        lines = err.decode('utf8').splitlines() + out.decode('utf8').splitlines()\n        vd.push(TextSheet(' '.join(args), source=lines))\n\n\n@VisiData.api\ndef open_dir(vd, p):\n    return DirSheet(p.name, source=p)\n\n@VisiData.api\ndef open_fdir(vd, p):\n    return FileListSheet(p.name, source=p)\n\n@VisiData.api\ndef addShellColumns(vd, cmd, sheet):\n    shellcol = ColumnShell(cmd, source=sheet, width=0)\n    sheet.addColumnAtCursor(\n            shellcol,\n            Column(cmd+'_stdout', srccol=shellcol, getter=lambda col,row: col.srccol.getValue(row)[0]),\n            Column(cmd+'_stderr', srccol=shellcol, getter=lambda col,row: col.srccol.getValue(row)[1]))\n\n\nclass ColumnShell(Column):\n    def __init__(self, name, cmd=None, **kwargs):\n        super().__init__(name, **kwargs)\n        self.expr = cmd or name\n\n    @asynccache(lambda col,row: (col, col.sheet.rowid(row)))\n    def calcValue(self, row):\n        try:\n            import shlex\n            args = []\n            context = LazyComputeRow(self.source, row)\n            for arg in shlex.split(self.expr):\n                if arg.startswith('$'):\n                    args.append(shlex.quote(str(context[arg[1:]])))\n                else:\n                    args.append(arg)\n\n            p = subprocess.Popen([os.getenv('SHELL', 'bash'), '-c', ' '.join(args)],\n                    stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n            return p.communicate()\n        except Exception as e:\n            vd.exceptionCaught(e)\n\n\nclass DirSheet(Sheet):\n    'Sheet displaying directory, using ENTER to open a particular file.  Edited fields are applied to the filesystem.'\n    rowtype = 'files' # rowdef: Path\n    defer = True\n    columns = [\n        Column('directory',\n            getter=lambda col,row: str(row.parent) if str(row.parent) == '.' else str(row.parent) + '/',\n            setter=lambda col,row,val: col.sheet.moveFile(row, val)),\n        Column('filename',\n            getter=lambda col,row: row.name + row.suffix,\n            setter=lambda col,row,val: col.sheet.renameFile(row, val)),\n        Column('abspath', width=0, type=str,\n            getter=lambda col,row: row,\n            setter=lambda col,row,val: os.rename(row, val)),\n        Column('ext', getter=lambda col,row: row.is_dir() and '/' or row.ext),\n        Column('size', type=int,\n            getter=lambda col,row: filesize(row),\n            setter=lambda col,row,val: os.truncate(row, int(val))),\n        Column('modtime', type=date,\n            getter=lambda col,row: modtime(row),\n            setter=lambda col,row,val: os.utime(row, times=((row.stat().st_atime, float(val))))),\n        Column('owner', width=0,\n            getter=lambda col,row: pwd.getpwuid(row.stat().st_uid).pw_name,\n            setter=lambda col,row,val: os.chown(row, pwd.getpwnam(val).pw_uid, -1)),\n        Column('group', width=0,\n            getter=lambda col,row: grp.getgrgid(row.stat().st_gid).gr_name,\n            setter=lambda col,row,val: os.chown(row, -1, grp.getgrnam(val).pw_gid)),\n        Column('mode', width=0,\n            getter=lambda col,row: '{:o}'.format(row.stat().st_mode),\n            setter=lambda col,row,val: os.chmod(row, int(val, 8))),\n        Column('filetype', width=0, cache='async', getter=lambda col,row: subprocess.Popen(['file', '--brief', row], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0].strip()),\n    ]\n    nKeys = 2\n    _ordering = [('modtime', True)]  # sort by reverse modtime initially\n\n    @staticmethod\n    def colorOwner(sheet, col, row, val):\n        ret = ''\n        if col.name == 'group':\n            mode = row.stat().st_mode\n            if mode & stat.S_IXGRP: ret = 'bold '\n            if mode & stat.S_IWGRP: return ret + 'green'\n            if mode & stat.S_IRGRP: return ret + 'yellow'\n        elif col.name == 'owner':\n            mode = row.stat().st_mode\n            if mode & stat.S_IXUSR: ret = 'bold '\n            if mode & stat.S_IWUSR: return ret + 'green'\n            if mode & stat.S_IRUSR: return ret + 'yellow'\n\n    def moveFile(self, row, newparent):\n        parent = Path(newparent)\n        newpath = Path(parent/(row.name + row.suffix))\n        if parent.exists():\n            if not parent.is_dir():\n                vd.error('destination %s not a directory' % parent)\n        else:\n            with contextlib.suppress(FileExistsError):\n                os.makedirs(parent)\n\n        row.rename(newpath)\n        row.given = newpath # modify visidata.Path\n        self.restat()\n\n    def renameFile(self, row, val):\n        newpath = row.with_name(val)\n        row.rename(newpath)\n        row.given = newpath\n        self.restat()\n\n    def removeFile(self, path):\n        if path.is_dir():\n            os.rmdir(path)\n        else:\n            path.unlink()\n\n    def deleteSourceRow(self, r):\n        self.removeFile(r)\n\n    def newRow(self):\n        vd.fail('new file not supported')\n\n    def iterload(self):\n        hidden_files = self.options.dir_hidden\n\n        def _walkfiles(p):\n            basepath = str(p)\n            for folder, subdirs, files in os.walk(basepath):\n                subfolder = folder[len(basepath)+1:]\n                if not hidden_files and subfolder.startswith('.'): continue\n                if subfolder in ['.', '..']: continue\n\n                fpath = Path(folder)\n                yield fpath\n\n                for fn in files:\n                    yield fpath/fn\n\n        def _listfiles(p):\n            basepath = str(p)\n            for fn in os.listdir(basepath):\n                yield p/fn\n\n\n        basepath = str(self.source)\n\n        folders = set()\n        f = _walkfiles if self.options.dir_recurse else _listfiles\n\n        for p in f(self.source):\n            if not hidden_files and p.name.startswith('.'):\n                continue\n\n            yield p\n\n    def preloadHook(self):\n        super().preloadHook()\n        Path.stat.cache_clear()\n\n    def restat(self):\n        vstat.cache_clear()\n\n    @asyncthread\n    def putChanges(self):\n        self.commitAdds()\n        self.commitMods()\n        self.commitDeletes()\n\n        self._deferredDels.clear()\n        self.reload()\n\n\nclass FileListSheet(DirSheet):\n    _ordering = []\n    def iterload(self):\n        for fn in self.source.open_text():\n            yield Path(fn.rstrip())\n\n\n@VisiData.api\ndef inputShell(vd):\n    cmd = vd.input(\"sh$ \", type=\"sh\")\n    if '$' not in cmd:\n        vd.warning('no $column in command')\n    return cmd\n\nBaseSheet.addCommand('', 'open-dir-current', 'vd.push(vd.currentDirSheet)', 'open Directory Sheet: browse properties of files in current directory')\n\nSheet.addCommand('z;', 'addcol-sh', 'cmd=inputShell(); addShellColumns(cmd, sheet)', 'create new column from bash expression, with $columnNames as variables')\n\nDirSheet.addCommand(ENTER, 'open-row', 'vd.push(openSource(cursorRow or fail(\"no row\"), filetype=\"dir\" if cursorRow.is_dir() else LazyComputeRow(sheet, cursorRow).ext))', 'open current file as a new sheet')\nDirSheet.addCommand('g'+ENTER, 'open-rows', 'for r in selectedRows: vd.push(openSource(r))', 'open selected files as new sheets')\nDirSheet.addCommand('^O', 'sysopen-row', 'launchEditor(cursorRow)', 'open current file in external $EDITOR')\nDirSheet.addCommand('g^O', 'sysopen-rows', 'launchEditor(*selectedRows)', 'open selected files in external $EDITOR')\n\nDirSheet.addCommand('y', 'copy-row', 'copy_files([cursorRow], inputPath(\"copy to dest: \"))', 'copy file to given directory')\nDirSheet.addCommand('gy', 'copy-selected', 'copy_files(selectedRows, inputPath(\"copy to dest: \", value=cursorRow.given))', 'copy selected files to given directory')\n\n@DirSheet.api\n@asyncthread\ndef copy_files(sheet, paths, dest):\n    destdir = Path(dest)\n    destdir.is_dir() or vd.fail('target must be directory')\n    vd.status('copying %s %s to %s' % (len(paths), sheet.rowtype, destdir))\n    os.makedirs(destdir, exist_ok=True)\n    for srcpath in Progress(paths, gerund='copying'):\n        try:\n            destpath = destdir/str(srcpath._path.name)\n            if srcpath.is_dir():\n                shutil.copy_tree(srcpath, destpath)\n            else:\n                shutil.copyfile(srcpath, destpath)\n        except Exception as e:\n            vd.exceptionCaught(e)\n\n\nvd.addGlobals({\n    'DirSheet': DirSheet\n})\n", "path": "visidata/shell.py"}]}