File: zip.vim

package info (click to toggle)
vim 2%3A9.1.2103-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 93,456 kB
  • sloc: ansic: 433,730; cpp: 6,399; makefile: 4,597; sh: 2,397; java: 2,312; xml: 2,099; python: 1,595; perl: 1,419; awk: 730; lisp: 501; cs: 458; objc: 369; sed: 8; csh: 6; haskell: 1
file content (581 lines) | stat: -rw-r--r-- 19,604 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
  " zip.vim: Handles browsing zipfiles
" AUTOLOAD PORTION
" Date:		2024 Aug 21
" Version:	34
" Maintainer:	This runtime file is looking for a new maintainer.
" Former Maintainer:	Charles E Campbell
" Last Change:
" 2024 Jun 16 by Vim Project: handle whitespace on Windows properly (#14998)
" 2024 Jul 23 by Vim Project: fix 'x' command
" 2024 Jul 24 by Vim Project: use delete() function
" 2024 Jul 30 by Vim Project: fix opening remote zipfile
" 2024 Aug 04 by Vim Project: escape '[' in name of file to be extracted
" 2024 Aug 05 by Vim Project: workaround for the FreeBSD's unzip
" 2024 Aug 05 by Vim Project: clean-up and make it work with shellslash on Windows
" 2024 Aug 18 by Vim Project: correctly handle special globbing chars
" 2024 Aug 21 by Vim Project: simplify condition to detect MS-Windows
" 2025 Mar 11 by Vim Project: handle filenames with leading '-' correctly
" 2025 Jul 12 by Vim Project: drop ../ on write to prevent path traversal attacks
" 2025 Sep 22 by Vim Project: support PowerShell Core
" 2025 Dec 20 by Vim Project: use :lcd instead of :cd
" License:	Vim License  (see vim's :help license)
" Copyright:	Copyright (C) 2005-2019 Charles E. Campbell {{{1
"		Permission is hereby granted to use and distribute this code,
"		with or without modifications, provided that this copyright
"		notice is copied with it. Like anything else that's free,
"		zip.vim and zipPlugin.vim are provided *as is* and comes with
"		no warranty of any kind, either expressed or implied. By using
"		this plugin, you agree that in no event will the copyright
"		holder be liable for any damages resulting from the use
"		of this software.

" ---------------------------------------------------------------------
" Load Once: {{{1
if &cp || exists("g:loaded_zip")
 finish
endif
let g:loaded_zip= "v34"
let s:keepcpo= &cpo
set cpo&vim

let s:zipfile_escape = ' ?&;\'
let s:ERROR          = 2
let s:WARNING        = 1
let s:NOTE           = 0

" ---------------------------------------------------------------------
"  Global Values: {{{1
if !exists("g:zip_shq")
 if &shq != ""
  let g:zip_shq= &shq
 elseif has("unix")
  let g:zip_shq= "'"
 else
  let g:zip_shq= '"'
 endif
endif
if !exists("g:zip_zipcmd")
 let g:zip_zipcmd= "zip"
endif
if !exists("g:zip_unzipcmd")
 let g:zip_unzipcmd= "unzip"
endif
if !exists("g:zip_extractcmd")
 let g:zip_extractcmd= g:zip_unzipcmd
endif

" ---------------------------------------------------------------------
"  required early
" s:Mess: {{{2
fun! s:Mess(group, msg)
  redraw!
  exe "echohl " . a:group
  echomsg a:msg
  echohl Normal
endfun

if v:version < 901
 " required for defer
 call s:Mess('WarningMsg', "***warning*** this version of zip needs vim 9.1 or later")
 finish
endif
" sanity checks
if !executable(g:zip_unzipcmd) && &shell !~ 'pwsh'
 call s:Mess('Error', "***error*** (zip#Browse) unzip not available on your system")
 finish
endif
if !dist#vim#IsSafeExecutable('zip', g:zip_unzipcmd) && &shell !~ 'pwsh'
 call s:Mess('Error', "Warning: NOT executing " .. g:zip_unzipcmd .. " from current directory!")
 finish
endif

" ----------------
"  PowerShell: {{{1
" ----------------

function! s:TryExecGnuFallBackToPs(executable, gnu_func_call, ...)
  " Check that a gnu executable is available, run the gnu_func_call if so. If
  " the gnu executable is not available or if gnu_func_call fails, try
  " ps_func_call if &shell =~ 'pwsh'. If all attempts fail, print errors.
  " a:executable - one of (g:zip_zipcmd, g:zip_unzipcmd, g:zip_extractcmd)
  " a:gnu_func_call - (string) a gnu function call to execute
  " a:1 - (optional string) a PowerShell function call to execute.
  let failures = []
  if executable(substitute(a:executable,'\s\+.*$','',''))
    try
      exe a:gnu_func_call
      return
    catch
      call add(failures, 'Failed to execute '.a:gnu_func_call)
    endtry
  else
    call add(failures, a:executable.' not available on your system')
  endif
  if &shell =~ 'pwsh' && a:0 == 1
    try
      exe a:1
      return
    catch
      call add(failures, 'Fallback to PowerShell attempted but failed')
    endtry
  endif
  for msg in failures
    call s:Mess('Error', msg)
  endfor
endfunction


function! s:ZipBrowsePS(zipfile)
  " Browse the contents of a zip file using PowerShell's
  " Equivalent `unzip -Z1 -- zipfile`
  let cmds = [
        \ '$zip = [System.IO.Compression.ZipFile]::OpenRead(' . s:Escape(a:zipfile, 1) . ');',
        \ '$zip.Entries | ForEach-Object { $_.FullName };',
        \ '$zip.Dispose()'
        \ ]
  return 'pwsh -NoProfile -Command ' . s:Escape(join(cmds, ' '), 1)
endfunction

function! s:ZipReadPS(zipfile, fname, tempfile)
  " Read a filename within a zipped file to a temporary file.
  " Equivalent to `unzip -p -- zipfile fname > tempfile`
  if a:fname =~ '/'
    call s:Mess('WarningMsg', "***warning*** PowerShell can display, but cannot update, files in archive subfolders")
  endif
  let cmds = [
        \ '$zip = [System.IO.Compression.ZipFile]::OpenRead(' . s:Escape(a:zipfile, 1) . ');',
        \ '$fileEntry = $zip.Entries | Where-Object { $_.FullName -eq ' . s:Escape(a:fname, 1) . ' };',
        \ '$stream = $fileEntry.Open();',
        \ '$fileStream = [System.IO.File]::Create(' . s:Escape(a:tempfile, 1) . ');',
        \ '$stream.CopyTo($fileStream);',
        \ '$fileStream.Close();',
        \ '$stream.Close();',
        \ '$zip.Dispose()'
        \ ]
  return 'pwsh -NoProfile -Command ' . s:Escape(join(cmds, ' '), 1)
endfunction

function! s:ZipUpdatePS(zipfile, fname)
  " Update a filename within a zipped file
  " Equivalent to `zip -u zipfile fname`
  if a:fname =~ '/'
    call s:Mess('Error', "***error*** PowerShell cannot update files in archive subfolders")
    return ':'
  endif
  return 'Compress-Archive -Path ' . a:fname . ' -Update -DestinationPath ' . a:zipfile
endfunction

function! s:ZipExtractFilePS(zipfile, fname)
  " Extract a single file from an archive
  " Equivalent to `unzip -o zipfile fname`
  if a:fname =~ '/'
    call s:Mess('Error', "***error*** PowerShell cannot extract files in archive subfolders")
    return ':'
  endif
  let cmds = [
        \ '$zip = [System.IO.Compression.ZipFile]::OpenRead(' . s:Escape(a:zipfile, 1) . ');',
        \ '$fileEntry = $zip.Entries | Where-Object { $_.FullName -eq ' . a:fname . ' };',
        \ '$stream = $fileEntry.Open();',
        \ '$fileStream = [System.IO.File]::Create(' . a:fname . ');',
        \ '$stream.CopyTo($fileStream);',
        \ '$fileStream.Close();',
        \ '$stream.Close();',
        \ '$zip.Dispose()'
        \ ]
  return 'pwsh -NoProfile -Command ' . s:Escape(join(cmds, ' '), 1)
endfunction

function! s:ZipDeleteFilePS(zipfile, fname)
  " Delete a single file from an archive
  " Equivalent to `zip -d zipfile fname`
  let cmds = [
        \ 'Add-Type -AssemblyName System.IO.Compression.FileSystem;',
        \ '$zip = [System.IO.Compression.ZipFile]::Open(' . s:Escape(a:zipfile, 1) . ', ''Update'');',
        \ '$entry = $zip.Entries | Where-Object { $_.Name -eq ' . s:Escape(a:fname, 1) . ' };',
        \ 'if ($entry) { $entry.Delete(); $zip.Dispose() }',
        \ 'else { $zip.Dispose() }'
        \ ]
  return 'pwsh -NoProfile -Command ' . s:Escape(join(cmds, ' '), 1)
endfunction

" ----------------
"  Functions: {{{1
" ----------------

" ---------------------------------------------------------------------
" zip#Browse: {{{2
fun! zip#Browse(zipfile)
  " sanity check: ensure that the zipfile has "PK" as its first two letters
  "               (zip files have a leading PK as a "magic cookie")
  if filereadable(a:zipfile) && readblob(a:zipfile, 0, 2) != 0z50.4B
   exe "noswapfile noautocmd e " .. fnameescape(a:zipfile)
   return
  endif

  let dict = s:SetSaneOpts()
  defer s:RestoreOpts(dict)

  " sanity checks
  if !executable(g:zip_unzipcmd) && &shell !~ 'pwsh'
   call s:Mess('Error', "***error*** (zip#Browse) unzip not available on your system")
   return
  endif
  if !filereadable(a:zipfile)
   if a:zipfile !~# '^\a\+://'
    " if it's an url, don't complain, let url-handlers such as vim do its thing
    call s:Mess('Error', "***error*** (zip#Browse) File not readable <".a:zipfile.">")
   endif
   return
  endif
  if &ma != 1
   set ma
  endif
  let b:zipfile= a:zipfile

  setlocal noswapfile
  setlocal buftype=nofile
  setlocal bufhidden=hide
  setlocal nobuflisted
  setlocal nowrap

  " Oct 12, 2021: need to re-use Bram's syntax/tar.vim.
  " Setting the filetype to zip doesn't do anything (currently),
  " but it is perhaps less confusing to curious perusers who do
  " a :echo &ft
  setf zip
  run! syntax/tar.vim

  " give header
  call append(0, ['" zip.vim version '.g:loaded_zip,
 \                '" Browsing zipfile '.a:zipfile,
 \                '" Select a file with cursor and press ENTER'])
  keepj $

  let gnu_cmd = "keepj sil r! " . g:zip_unzipcmd . " -Z1 -- " . s:Escape(a:zipfile, 1)
  let ps_cmd = 'keepj sil r! ' . s:ZipBrowsePS(a:zipfile)
  call s:TryExecGnuFallBackToPs(g:zip_unzipcmd, gnu_cmd, ps_cmd)

  if v:shell_error != 0
   call s:Mess('WarningMsg', "***warning*** (zip#Browse) ".fnameescape(a:zipfile)." is not a zip file")
   keepj sil! %d
   let eikeep= &ei
   set ei=BufReadCmd,FileReadCmd
   exe "keepj r ".fnameescape(a:zipfile)
   let &ei= eikeep
   keepj 1d
   return
  endif

  " Maps associated with zip plugin
  setlocal noma nomod ro
  noremap <silent> <buffer>	<cr>		:call <SID>ZipBrowseSelect()<cr>
  noremap <silent> <buffer>	x		:call zip#Extract()<cr>
  if &mouse != ""
   noremap <silent> <buffer>	<leftmouse>	<leftmouse>:call <SID>ZipBrowseSelect()<cr>
  endif

endfun

" ---------------------------------------------------------------------
" ZipBrowseSelect: {{{2
fun! s:ZipBrowseSelect()
  let dict = s:SetSaneOpts()
  defer s:RestoreOpts(dict)
  let fname= getline(".")
  if !exists("b:zipfile")
   return
  endif

  " sanity check
  if fname =~ '^"'
   return
  endif
  if fname =~ '/$'
   call s:Mess('Error', "***error*** (zip#Browse) Please specify a file, not a directory")
   return
  endif

  " get zipfile to the new-window
  let zipfile = b:zipfile
  let curfile = expand("%")

  noswapfile new
  if !exists("g:zip_nomax") || g:zip_nomax == 0
   wincmd _
  endif
  let s:zipfile_{winnr()}= curfile
  exe "noswapfile e ".fnameescape("zipfile://".zipfile.'::'.fname)
  filetype detect

endfun

" ---------------------------------------------------------------------
" zip#Read: {{{2
fun! zip#Read(fname,mode)
  let dict = s:SetSaneOpts()
  defer s:RestoreOpts(dict)

  if has("unix")
   let zipfile = substitute(a:fname,'zipfile://\(.\{-}\)::[^\\].*$','\1','')
   let fname   = substitute(a:fname,'zipfile://.\{-}::\([^\\].*\)$','\1','')
  else
   let zipfile = substitute(a:fname,'^.\{-}zipfile://\(.\{-}\)::[^\\].*$','\1','')
   let fname   = substitute(a:fname,'^.\{-}zipfile://.\{-}::\([^\\].*\)$','\1','')
  endif
  let fname    = fname->substitute('[', '[[]', 'g')->escape('?*\\')
  " sanity check
  if !executable(substitute(g:zip_unzipcmd,'\s\+.*$','',''))  && &shell !~ 'pwsh'
   call s:Mess('Error', "***error*** (zip#Read) sorry, your system doesn't appear to have the ".g:zip_unzipcmd." program")
   return
  endif

  " the following code does much the same thing as
  "   exe "keepj sil! r! ".g:zip_unzipcmd." -p -- ".s:Escape(zipfile,1)." ".s:Escape(fname,1)
  " but allows zipfile://... entries in quickfix lists
  let temp = tempname()
  let fn   = expand('%:p')

  let gnu_cmd = 'sil !' . g:zip_unzipcmd . ' -p -- ' . s:Escape(zipfile, 1) . ' ' . s:Escape(fname, 1) . ' > ' . s:Escape(temp, 1)
  let ps_cmd = 'sil !' . s:ZipReadPS(zipfile, fname, temp)
  call s:TryExecGnuFallBackToPs(g:zip_unzipcmd, gnu_cmd, ps_cmd)

  sil exe 'keepalt file '.temp
  sil keepj e!
  sil exe 'keepalt file '.fnameescape(fn)
  call delete(temp)

  filetype detect

  " cleanup
  set nomod

endfun

" ---------------------------------------------------------------------
" zip#Write: {{{2
fun! zip#Write(fname)
  let dict = s:SetSaneOpts()
  let need_rename = 0
  defer s:RestoreOpts(dict)

  " sanity checks
  if !executable(substitute(g:zip_zipcmd,'\s\+.*$','','')) && &shell !~ 'pwsh'
    call s:Mess('Error', "***error*** (zip#Write) sorry, your system doesn't appear to have the ".g:zip_zipcmd." program")
    return
  endif

  let curdir= getcwd()
  let tmpdir= tempname()
  if tmpdir =~ '\.'
    let tmpdir= substitute(tmpdir,'\.[^.]*$','','e')
  endif
  call mkdir(tmpdir,"p")

  " attempt to change to the indicated directory
  if s:ChgDir(tmpdir,s:ERROR,"(zip#Write) cannot lcd to temporary directory")
    return
  endif

  " place temporary files under .../_ZIPVIM_/
  if isdirectory("_ZIPVIM_")
    call delete("_ZIPVIM_", "rf")
  endif
  call mkdir("_ZIPVIM_")
  lcd _ZIPVIM_

  if has("unix")
    let zipfile = substitute(a:fname,'zipfile://\(.\{-}\)::[^\\].*$','\1','')
    let fname   = substitute(a:fname,'zipfile://.\{-}::\([^\\].*\)$','\1','')
  else
    let zipfile = substitute(a:fname,'^.\{-}zipfile://\(.\{-}\)::[^\\].*$','\1','')
    let fname   = substitute(a:fname,'^.\{-}zipfile://.\{-}::\([^\\].*\)$','\1','')
  endif
  if fname =~ '^[.]\{1,2}/'
    let gnu_cmd = g:zip_zipcmd . ' -d ' . s:Escape(fnamemodify(zipfile,":p"),0) . ' ' . s:Escape(fname,0) 
    let gnu_cmd = 'call system(''' . substitute(gnu_cmd, "'", "''", 'g') . ''')'
    let ps_cmd = $"call system({s:Escape(s:ZipDeleteFilePS(zipfile, fname), 1)})"
    call s:TryExecGnuFallBackToPs(g:zip_zipcmd, gnu_cmd, ps_cmd)
    let fname = fname->substitute('^\([.]\{1,2}/\)\+', '', 'g')
    let need_rename = 1
  endif

  if fname =~ '/'
    let dirpath = substitute(fname,'/[^/]\+$','','e')
    if has("win32unix") && executable("cygpath")
    let dirpath = substitute(system("cygpath ".s:Escape(dirpath,0)),'\n','','e')
    endif
    call mkdir(dirpath,"p")
  endif
  if zipfile !~ '/'
    let zipfile= curdir.'/'.zipfile
  endif

  " don't overwrite files forcefully
  exe "w ".fnameescape(fname)
  if has("win32unix") && executable("cygpath")
    let zipfile = substitute(system("cygpath ".s:Escape(zipfile,0)),'\n','','e')
  endif

  if (has("win32") || has("win95") || has("win64") || has("win16")) && &shell !~? 'sh$'
    let fname = substitute(fname, '[', '[[]', 'g')
  endif

  let gnu_cmd = g:zip_zipcmd . ' -u '. s:Escape(fnamemodify(zipfile,":p"),0) . ' ' . s:Escape(fname,0) 
  let gnu_cmd = 'call system(''' . substitute(gnu_cmd, "'", "''", 'g') . ''')'
  let ps_cmd = s:ZipUpdatePS(s:Escape(fnamemodify(zipfile, ':p'), 0), s:Escape(fname, 0))
  let ps_cmd = 'call system(''' . substitute(ps_cmd, "'", "''", 'g') . ''')'
  call s:TryExecGnuFallBackToPs(g:zip_zipcmd, gnu_cmd, ps_cmd)
  if &shell =~ 'pwsh'
    " Vim flashes 'creation in progress ...' from what I believe is the
    " ProgressAction stream of PowerShell. Unfortunately, this cannot be
    " suppressed (as of 250824) due to an open PowerShell issue.
    " https://github.com/PowerShell/PowerShell/issues/21074
    " This necessitates a redraw of the buffer.
    redraw!
  endif

  if v:shell_error != 0
    call s:Mess('Error', "***error*** (zip#Write) sorry, unable to update ".zipfile." with ".fname)

  elseif s:zipfile_{winnr()} =~ '^\a\+://'
    " support writing zipfiles across a network
    let netzipfile= s:zipfile_{winnr()}
    1split|enew
    let binkeep= &binary
    let eikeep = &ei
    set binary ei=all
    exe "noswapfile e! ".fnameescape(zipfile)
    call netrw#NetWrite(netzipfile)
    let &ei     = eikeep
    let &binary = binkeep
    q!
    unlet s:zipfile_{winnr()}
  elseif need_rename
    exe $"sil keepalt file {fnameescape($"zipfile://{zipfile}::{fname}")}"
    call s:Mess('Warning', "***error*** (zip#Browse) Path Traversal Attack detected, dropping relative path")
  endif

  " cleanup and restore current directory
  lcd ..
  call delete("_ZIPVIM_", "rf")
  call s:ChgDir(curdir,s:WARNING,"(zip#Write) unable to return to ".curdir."!")
  call delete(tmpdir, "rf")
  setlocal nomod
endfun

" ---------------------------------------------------------------------
" zip#Extract: extract a file from a zip archive {{{2
fun! zip#Extract()

  let dict = s:SetSaneOpts()
  defer s:RestoreOpts(dict)
  let fname= getline(".")

  " sanity check
  if fname =~ '^"'
    return
  endif
  if fname =~ '/$'
    call s:Mess('Error', "***error*** (zip#Extract) Please specify a file, not a directory")
    return
  elseif fname =~ '^[.]\?[.]/'
    call s:Mess('Error', "***error*** (zip#Browse) Path Traversal Attack detected, not extracting!")
    return
  endif
  if filereadable(fname)
    call s:Mess('Error', "***error*** (zip#Extract) <" .. fname .."> already exists in directory, not overwriting!")
    return
  endif
  let target = fname->substitute('\[', '[[]', 'g')
  " unzip 6.0 does not support -- to denote end-of-arguments
  " unzip 6.1 (2010) apparently supports, it, but hasn't been released
  " so the workaround is to use glob '[-]' so that it won't be considered an argument
  " else, it would be possible to use 'unzip -o <file.zip> '-d/tmp' to extract the whole archive
  let target = target->substitute('^-', '[&]', '')
  if &shell =~ 'cmd' && has("win32")
    let target = target
		\ ->substitute('[?*]', '[&]', 'g')
		\ ->substitute('[\\]', '?', 'g')
		\ ->shellescape()
    " there cannot be a file name with '\' in its name, unzip replaces it by _
    let fname = fname->substitute('[\\?*]', '_', 'g')
  else
    let target = target->escape('*?\\')->shellescape()
  endif

  " extract the file mentioned under the cursor
  let gnu_cmd = g:zip_extractcmd . ' -o '. shellescape(b:zipfile) . ' ' . target
  let gnu_cmd = 'call system(''' . substitute(gnu_cmd, "'", "''", 'g') . ''')'
  let ps_cmd = $"call system({s:Escape(s:ZipExtractFilePS(b:zipfile, target), 1)})"
  call s:TryExecGnuFallBackToPs(g:zip_extractcmd, gnu_cmd, ps_cmd)

  if v:shell_error != 0
    call s:Mess('Error', "***error*** ".g:zip_extractcmd." ".b:zipfile." ".fname.": failed!")
  elseif !filereadable(fname) && &shell !~ 'pwsh'
    call s:Mess('Error', "***error*** attempted to extract ".fname." but it doesn't appear to be present!")
  else
    echomsg "***note*** successfully extracted ".fname
  endif
endfun

" ---------------------------------------------------------------------
" s:Escape: {{{2
fun! s:Escape(fname,isfilt)
  if exists("*shellescape")
   if a:isfilt
    let qnameq= shellescape(a:fname,1)
   else
    let qnameq= shellescape(a:fname)
   endif
  else
   let qnameq= g:zip_shq.escape(a:fname,g:zip_shq).g:zip_shq
  endif
  return qnameq
endfun

" ---------------------------------------------------------------------
" s:ChgDir: {{{2
fun! s:ChgDir(newdir,errlvl,errmsg)
  try
   exe "lcd ".fnameescape(a:newdir)
  catch /^Vim\%((\a\+)\)\=:E344/
   redraw!
   if a:errlvl == s:NOTE
    echomsg "***note*** ".a:errmsg
   elseif a:errlvl == s:WARNING
    call s:Mess("WarningMsg", "***warning*** ".a:errmsg)
   elseif a:errlvl == s:ERROR
    call s:Mess("Error", "***error*** ".a:errmsg)
   endif
   return 1
  endtry

  return 0
endfun

" ---------------------------------------------------------------------
" s:SetSaneOpts: {{{2
fun! s:SetSaneOpts()
  let dict = {}
  let dict.report = &report
  let dict.shellslash = &shellslash

  let &report = 10
  let &shellslash = 0

  return dict
endfun

" ---------------------------------------------------------------------
" s:RestoreOpts: {{{2
fun! s:RestoreOpts(dict)
  for [key, val] in items(a:dict)
    exe $"let &{key} = {val}"
  endfor
endfun

" ------------------------------------------------------------------------
" Modelines And Restoration: {{{1
let &cpo= s:keepcpo
unlet s:keepcpo
" vim:ts=8 fdm=marker