summaryrefslogtreecommitdiffstats
path: root/runtime/autoload/zip.vim
blob: 4a53fc5f28656aa7bae886240e06a57702bde7e2 (plain)
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
" 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
" 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)
 call s:Mess('Error', "***error*** (zip#Browse) unzip not available on your system")
 finish
endif
if !dist#vim#IsSafeExecutable('zip', g:zip_unzipcmd)
 call s:Mess('Error', "Warning: NOT executing " .. g:zip_unzipcmd .. " from current directory!")
 finish
endif

" ----------------
"  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)
   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 $

  exe $"keepj sil r! {g:zip_unzipcmd} -Z1 -- {s:Escape(a:zipfile, 1)}"
  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\+.*$','',''))
   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')
  exe "sil !".g:zip_unzipcmd." -p -- ".s:Escape(zipfile,1)." ".s:Escape(fname,1).' > '.temp
  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()
  defer s:RestoreOpts(dict)

  " sanity checks
  if !executable(substitute(g:zip_zipcmd,'\s\+.*$','',''))
   call s:Mess('Error', "***error*** (zip#Write) sorry, your system doesn't appear to have the ".g:zip_zipcmd." program")
   return
  endif
  if !exists("*mkdir")
   call s:Mess('Error', "***error*** (zip#Write) sorry, mkdir() doesn't work on your system")
   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 cd to temporary directory")
   return
  endif

  " place temporary files under .../_ZIPVIM_/
  if isdirectory("_ZIPVIM_")
   call delete("_ZIPVIM_", "rf")
  endif
  call mkdir("_ZIPVIM_")
  cd _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 =~ '/'
   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

  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

  call system(g:zip_zipcmd." -u ".s:Escape(fnamemodify(zipfile,":p"),0)." ".s:Escape(fname,0))
  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()}
  endif

  " cleanup and restore current directory
  cd ..
  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
  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')
  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
  call system($"{g:zip_extractcmd} -o {shellescape(b:zipfile)} {target}")
  if v:shell_error != 0
   call s:Mess('Error', "***error*** ".g:zip_extractcmd." ".b:zipfile." ".fname.": failed!")
  elseif !filereadable(fname)
   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 "cd ".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