1"
2" Filename: cream-replacemulti.vim
3" Updated:  2005-01-20 22:32:47-0400
4"
5" Cream -- An easy-to-use configuration of the famous Vim text editor
6" [ http://cream.sourceforge.net ] Copyright (C) 2001-2011 Steve Hall
7"
8" License: {{{1
9" This program is free software; you can redistribute it and/or modify
10" it under the terms of the GNU General Public License as published by
11" the Free Software Foundation; either version 3 of the License, or
12" (at your option) any later version.
13" [ http://www.gnu.org/licenses/gpl.html ]
14"
15" This program is distributed in the hope that it will be useful, but
16" WITHOUT ANY WARRANTY; without even the implied warranty of
17" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18" General Public License for more details.
19"
20" You should have received a copy of the GNU General Public License
21" along with this program; if not, write to the Free Software
22" Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
23" 02111-1307, USA.
24"
25" Description: {{{1
26"
27" This script replaces text across multiple files. It is a pure Vim
28" implementation and does not depend on external utilties that do
29" similar things (like sed, grep, cat, or Find). As such, it is
30" cross-platform and has been tested on both Windows95/XP and Linux.
31"
32" Replace Multi is intended to be used in the GUI version of Vim,
33" gVim. It presents a rather hackish dialog to take user input for the
34" Find, Replace, and Path components and present options for Case
35" Sensitivity and the use of Vim's Regular Expressions. (Some day in
36" the future it might be adapted to work from the command line without
37" a dialog.)
38"
39" In testing, Replace Multi performed a replacment operation on 1000
40" small files in 25 seconds. (2.80Ghz Windows XP machine with 1Gb of
41" RAM.) Obviously various Vim and system settings could vary greatly,
42" but as a benchmark this is about 40 files/second.
43"
44" ToDos: {{{1
45"
46" Other (potential) bells and whistles to be added:
47" * Actual testing of non-alpha/numeric characters. ;)
48" * Search subdirectories option
49" * Provide a commandline version
50" * Prevent files from showing up on Recent File or Buffers menu
51"
52" 1}}}
53"
54" Dependencies:
55" ********************************************************************
56" This script depends on Multivals, available from:
57"   http://www.vim.org/script.php?script_id=171.
58" ********************************************************************
59"
60" Cream_replacemulti() {{{1
61function! Cream_replacemulti()
62" Main function
63
64	" save guioptions environment
65	let myguioptions = &guioptions
66	" do not use a vertical layout for dialog buttons
67	set guioptions-=v
68
69	" initialize variables if don't exist (however, remember if they do!)
70	if !exists("g:CREAM_RMFIND")
71		let g:CREAM_RMFIND = 'Find Me!'
72	endif
73	if !exists("g:CREAM_RMREPL")
74		let g:CREAM_RMREPL = 'Replace Me!'
75	endif
76	" let's always get the current directory and don't remember previous(good idea?)
77	" * Would be good if we could somehow have another button on the
78	"   inputdialog() box for path that reads "Get Current", but this function
79	"   doesn't have this feature (yet).
80	if !exists("g:CREAM_RMPATH")
81		" hmmm... buggy
82		"let g:CREAM_RMPATH = Cream_path_fullsystem(getcwd(), "unix")
83		" try this...
84		if exists("$CREAM")
85			let g:CREAM_RMPATH = Cream_path_fullsystem(expand("%:p:h"), "unix")
86		else
87			let g:CREAM_RMPATH = s:Cream_path_fullsystem(expand("%:p:h"), "unix")
88		endif
89
90	endif
91
92	" option: case sensitive? (1=yes, 0=no)
93	if !exists("g:CREAM_RMCASE")
94		" initialize on
95		let g:CREAM_RMCASE = 0
96	endif
97
98	" option: regular expressions? (1=yes, 0=no)
99	if !exists("g:CREAM_RMREGEXP")
100		" initialize off
101		let g:CREAM_RMREGEXP = 0
102	endif
103
104	"*** No more warning
105	"" beta warning
106	"call confirm(
107	"    \ "NOTICE:\n" .
108	"    \ "\n" .
109	"    \ "This tool is still beta quality. Use at your own risk!\n" .
110	"    \ "\n" .
111	"    \ "(But please forward us any bugs you find!)\n",
112	"    \ "&Ok", 1, "Warning")
113	"***
114
115	" get find, replace and path/filename info (verifying that Find and Path
116	" aren't empty, too)
117	let valid = 0
118	while valid == 0
119		" get find, replace and path/filename info
120		let myreturn = s:Cream_replacemulti_request(g:CREAM_RMFIND, g:CREAM_RMREPL, g:CREAM_RMPATH)
121		" if quit code returned
122		if myreturn == 2
123			break
124		" verify criticals aren't empty (user pressed 'ok')
125		elseif myreturn == 1
126			let valid = s:Cream_replacemulti_verify()
127		endif
128	endwhile
129
130	" if not quit code, continue
131	if myreturn != 2
132		" warning
133		let myproceed = s:Cream_replacemulti_warning()
134""*** DEBUG:
135"let n = confirm(
136"    \ "DEBUG:\n" .
137"    \ "  g:CREAM_RMFIND = " . g:CREAM_RMFIND . "\n" .
138"    \ "  g:CREAM_RMREPL = " . g:CREAM_RMREPL . "\n" .
139"    \ "  g:CREAM_RMPATH = " . g:CREAM_RMPATH . "\n" .
140"    \ "  myreturn       = " . myreturn . "\n" .
141"    \ "  myproceed      = " . myproceed . "\n" .
142"    \ "\n", "&Ok\n&Cancel", 1, "Info")
143"if n != 1
144"    return
145"endif
146""***
147		if myproceed != 0
148			" DO FIND/REPLACE! (Critical file states are verified within.)
149			if valid == 1
150				call s:Cream_replacemulti_doit()
151			else
152				return
153			endif
154		else
155			" do nothing
156			return
157		endif
158	endif
159
160	" restore guioptions
161	execute "set guioptions=" . myguioptions
162
163endfunction
164
165
166" s:Cream_replacemulti_warning() {{{1
167function! s:Cream_replacemulti_warning()
168" simple death/destruction warning, last chance to bail
169
170	let msg = "Warning! This action is about modify the contents of\n" .
171			\ "multiple files based on the previous dialog's settings.\n"
172	let msg = msg . "\n"
173	let msg = msg . "  Continue?"
174	let mychoice = confirm(msg, "&Ok\n&Cancel", 1, "Info")
175	if mychoice == 0
176		return
177	elseif mychoice == 1
178		return 1
179	elseif mychoice == 2
180		return
181	else
182		" error
183		call confirm("Error in s:Cream_replacemulti_warning(). Unexpected result.", "&Ok", 1, "Error")
184	endif
185endfunction
186
187
188" s:Cream_replacemulti_request() {{{1
189function! s:Cream_replacemulti_request(myfind, myrepl, mypath)
190" Dialog to obtain user information (find, replace, path/filename)
191" * returns 0 for not finished (so can be recalled)
192" * returns 1 for finished
193" * returns 2 for quit
194
195	" escape ampersand with second ampersand in dialog box
196	" * JUST for display
197	" * Just for Windows
198	if      has("win32")
199		\|| has("win16")
200		\|| has("win95")
201		\|| has("dos16")
202		\|| has("dos32")
203		let myfind_fixed = substitute(a:myfind, "&", "&&", "g")
204		let myrepl_fixed = substitute(a:myrepl, "&", "&&", "g")
205	else
206		let myfind_fixed = a:myfind
207		let myrepl_fixed = a:myrepl
208	endif
209
210	let mychoice = 0
211	let msg = "INSTRUCTIONS:\n"
212	let msg = msg . "* Enter the literal find and replace text below.\n"
213	let msg = msg . "* Use \"\\n\" to represent new lines and \"\\t\" for tabs.\n"
214	let msg = msg . "* Use wildcards as needed when entering path and filename.\n"
215	let msg = msg . "* Please read the Vim help to understand regular expressions (:help regexp)\n"
216	let msg = msg . "\n"
217	let msg = msg . "\n"
218	let msg = msg . "FIND:                                           " . myfind_fixed . "  \n"
219	let msg = msg . "\n"
220	let msg = msg . "REPLACE:                                 " . myrepl_fixed . "  \n"
221	let msg = msg . "\n"
222	let msg = msg . "PATH and FILENAME(S):    " . a:mypath . "             \n"
223	let msg = msg . "\n"
224	let msg = msg . "\n"
225	let msg = msg . "OPTIONS:\n"
226	if g:CREAM_RMCASE == 1
227		let msg = msg . "               [X] Yes   [_] No          Case Sensitive\n"
228	else
229		let msg = msg . "               [_] Yes   [X] No          Case Sensitive\n"
230	endif
231	if g:CREAM_RMREGEXP == 1
232		let msg = msg . "               [X] Yes   [_] No          Regular Expressions\n"
233	else
234		let msg = msg . "               [_] Yes   [X] No          Regular Expressions\n"
235	endif
236	let msg = msg . "\n"
237	let mychoice = confirm(msg, "&Find\n&Replace\n&Path/Filename\nOp&tions\n&Ok\n&Cancel", 1, "Info")
238	if mychoice == 0
239		" quit via Esc, window close, etc. (OS specific)
240		return 2
241	elseif mychoice == 1
242		" call dialog to get find string
243		call s:Cream_replacemulti_find(g:CREAM_RMFIND)
244		return
245	elseif mychoice == 2
246		" call dialog to get replace string
247		call s:Cream_replacemulti_repl(g:CREAM_RMREPL)
248		return
249	elseif mychoice == 3
250		" call dialog to get path string
251		call s:Cream_replacemulti_path(g:CREAM_RMPATH)
252		return
253	elseif mychoice == 4
254		" call dialog to set options. Continue to show until Ok(1) or Cancel(2)
255		let valid = '0'
256		while valid == '0'
257			" let user set options
258			let myreturn = s:Cream_replacemulti_options()
259			" if Ok or Cancel, go back to main dialog
260			if myreturn == 1 || myreturn == 2
261				let valid = 1
262			endif
263		endwhile
264		return
265	elseif mychoice == 5
266		" user thinks ok, return positive for actual verification
267		return 1
268	elseif mychoice == 6
269		" quit
270		return 2
271	endif
272
273	call confirm("Error in s:Cream_replacemulti_request(). Unexpected result.", "&Ok", "Error")
274	return 2
275
276endfunction
277
278
279" Get input through dialog
280" * Would be nice to detect Ok or Cancel here. (Cancel returns an empty string.)
281" * These stupid spaces are to work around a Windows GUI problem: Input is only
282"   allowed to be as long as the actual input box. Therefore, we're widening the
283"   dialog box so the input box is wider. ;)
284" s:Cream_replacemulti_find() {{{1
285function! s:Cream_replacemulti_find(myfind)
286	let myfind = inputdialog("Please enter a string to find...   " .
287			\"                                                     " .
288			\"                                                     " .
289			\"                                                     " .
290			\"                                                     ", a:myfind)
291	" if user cancels, returns empty. Don't allow.
292	if myfind != ""
293		let g:CREAM_RMFIND = myfind
294	endif
295	return
296endfunction
297
298" s:Cream_replacemulti_repl() {{{1
299function! s:Cream_replacemulti_repl(myrepl)
300	let myrepl = inputdialog("Please enter a string to replace..." .
301			\"                                                     " .
302			\"                                                     " .
303			\"                                                     " .
304			\"                                                     ", a:myrepl)
305	" allow empty return, but verify not a cancel
306	if myrepl == ""
307		let mychoice = confirm(
308			\ "Replace was found empty.\n" .
309			\ "\n" .
310			\ "(This is legal, but was it your intention?)\n",
311			\ "&Leave\ empty\n&No,\ put\ back\ what\ I\ had", 2, "Question")
312		if mychoice == 2
313			" leave global as is
314		else
315			let g:CREAM_RMREPL = ""
316		endif
317	else
318		let g:CREAM_RMREPL = myrepl
319	endif
320	return
321endfunction
322
323" s:Cream_replacemulti_path() {{{1
324function! s:Cream_replacemulti_path(mypath)
325	let mypath = inputdialog("Please enter a path and filename... (Wildcards allowed.)" .
326			\"                                                     " .
327			\"                                                     " .
328			\"                                                     " .
329			\"                                                     ", a:mypath)
330	" if user cancels, returns empty. Don't allow.
331	if mypath != ""
332		let g:CREAM_RMPATH = mypath
333	endif
334	return
335endfunction
336
337" s:Cream_replacemulti_options() {{{1
338function! s:Cream_replacemulti_options()
339
340	let mychoice = 0
341	let msg = "Options:\n"
342	let msg = msg . "\n"
343	let msg = msg . "\n"
344	if g:CREAM_RMCASE == 1
345		let strcase = "X"
346	else
347		let strcase = "_"
348	endif
349	if g:CREAM_RMREGEXP == 1
350		let strregexp = "X"
351	else
352		let strregexp = "_"
353	endif
354	let msg = msg . "    [" . strcase . "]  Case Sensitive\n"
355	let msg = msg . "    [" . strregexp . "]  Regular Expressions\n"
356	let msg = msg . "\n"
357	let mychoice = confirm(msg, "Case\ Sensitive\nRegular\ Expressions\n&Ok", 1, "Info")
358	if mychoice == 0
359		" quit via Esc, window close, etc. (OS specific)
360		return 2
361	elseif mychoice == 1
362		" case sensitive
363		if g:CREAM_RMCASE == 1
364			let g:CREAM_RMCASE = 0
365		else
366			let g:CREAM_RMCASE = 1
367		endif
368		return
369	elseif mychoice == 2
370		" regular expressions
371		if g:CREAM_RMREGEXP == 1
372			let g:CREAM_RMREGEXP = 0
373		else
374			let g:CREAM_RMREGEXP = 1
375		endif
376		return
377	elseif mychoice == 3
378		" ok
379		return 1
380	endif
381	return
382endfunction
383
384
385" s:Cream_replacemulti_verify() {{{1
386function! s:Cream_replacemulti_verify()
387" Verify that Find and Path not empty (although Replace can be)
388" * Returns '1' when valid
389
390	if g:CREAM_RMFIND == ''
391		call confirm("Find may not be empty.", "&Ok", "Warning")
392		return
393	elseif g:CREAM_RMPATH == ''
394		call confirm("Path/Filename may not be empty.", "&Ok", "Warning")
395		return
396	else
397		return 1
398	endif
399endfunction
400
401
402" s:Cream_replacemulti_doit() {{{1
403function! s:Cream_replacemulti_doit()
404" Main find/replace function. Also validates files and state
405
406	" get files {{{2
407
408	let myfiles = ""
409
410	" get file list
411	" Note: Wildcard "*" for filename won't return files beginning with dot.
412	" (Must use ".*" to obtain.)
413	if exists("$CREAM")
414		let myfiles = Cream_path_fullsystem(glob(g:CREAM_RMPATH), "unix")
415	else
416		let myfiles = s:Cream_path_fullsystem(glob(g:CREAM_RMPATH), "unix")
417	endif
418
419	" (from explorer.vim)
420	" Add the dot files now, making sure "." is not included!
421	"let myfiles = substitute(Cream_path_fullsystem(glob(g:rmpath), "unix"), "[^\n]*/./\\=\n", '' , '')
422
423	" add a blank line at the end
424	if myfiles != "" && myfiles !~ '\n$'
425		let myfiles = myfiles . "\n"
426	endif
427
428	"
429	" create file list {{{2
430
431""*** DEBUG:
432"let n = confirm(
433"    \ "DEBUG:\n" .
434"    \ "  myfiles:\n" .
435"    \ myfiles . "\n" .
436"    \ "\n", "&Ok\n&Cancel", 1, "Info")
437"if n != 1
438"    return
439"endif
440""***
441
442	" count files (big assumption: number of newlines == number of files!)
443	let filecount = MvNumberOfElements(myfiles, "\n")
444	" initialize variables
445	let newline = "\n"
446	let mypos = 0
447	let myposnew = 0
448	let i = 0
449	" iterate through files (do while newline still found in listing)
450	while myposnew != -1
451		let myposnew = match(myfiles, newline, mypos)
452
453"*** DEBUG:
454"let temp = confirm("DEBUG:\n" .
455"			\"  filecount       = " . filecount . "\n" .
456"			\"  strlen(newline) = " . strlen(newline) . "\n" .
457"			\"  mypos           = " . mypos . "\n" .
458"			\"  myposnew        = " . myposnew . "\n" .
459"			\"  i               = " . i
460"			\, "&Ok\n&Cancel")
461"if temp == 2
462"	break
463"endif
464"***
465		if myposnew != -1
466			let i = i + 1
467			" get string
468			let myfile{i} = strpart(myfiles, mypos, myposnew - mypos)
469			" advance position to just after recently found newline
470			let mypos = myposnew + strlen(newline)
471
472"*** DEBUG:
473"call confirm("DEBUG:\n  File " . i . ":      " . "|" . myfile{i} . "|", "&Ok")
474"***
475		else
476			break
477		endif
478	endwhile
479
480	" list file count and file names if less than 25
481	if filecount > 25
482		let mychoice = confirm(
483			\"  Total number of files:   " . filecount . "\n" .
484			\"\n", "&Ok\n&Cancel", 1, "Info")
485	else
486		let mychoice = confirm(
487			\"  Total number of files:   " . filecount . "\n" .
488			\"\n" .
489			\"  Files to be modified...  \n" . myfiles . "\n" .
490			\"\n", "&Ok\n&Cancel", 1, "Info")
491	endif
492	" Prompt: continue?
493	if mychoice != 1
494		let filecount = 0
495		let myabort = 1
496	endif
497
498	"
499	" find/replace in files (validating files prior to operation) {{{2
500
501	if filecount == 0
502		if exists("myabort")
503			call confirm("Operation aborted.", "&Ok", "Info")
504		else
505			call confirm("No files were found to act upon!", "&Ok", "Info")
506		endif
507		return
508	endif
509
510	" strings
511	" * use local variable to maintain global strings
512	" * work around ridiculous differences between {pattern} and {string}
513	let myfind = g:CREAM_RMFIND
514	let myrepl = g:CREAM_RMREPL
515
516	" capture current magic state
517	let mymagic = &magic
518	" turn off
519	if mymagic == "1"
520		set nomagic
521	endif
522
523	" case-sensitive
524	if g:CREAM_RMCASE == 1
525		let mycase = "I"
526	else
527		let mycase = "i"
528	endif
529
530	" regular expressions
531	if g:CREAM_RMREGEXP == 1
532		let myregexp = ""
533		set magic
534	else
535		let myregexp = "\\V"
536		set nomagic
537
538		" escape strings
539		" escape all backslashes
540		" * This effectively eliminates ALL magical special pattern
541		"   meanings! Only those patterns "unescaped" at the next step
542		"   become effective. (Diehards are gonna kill us for this.)
543		let myfind = substitute(myfind, '\\', '\\\\', 'g')
544		let myrepl = substitute(myrepl, '\\', '\\\\', 'g')
545
546		" un-escape desired escapes
547		" * Anything not recovered here is escaped forever ;)
548		let myfind = substitute(myfind, '\\\\n', '\\n', 'g')
549		let myfind = substitute(myfind, '\\\\t', '\\t', 'g')
550		let myrepl = substitute(myrepl, '\\\\n', '\\n', 'g')
551		let myrepl = substitute(myrepl, '\\\\t', '\\t', 'g')
552
553		" escape slashes so as not to thwart the :s command below!
554		let myfind = substitute(myfind, '/', '\\/', 'g')
555		let myrepl = substitute(myrepl, '/', '\\/', 'g')
556
557		" replace string requires returns instead of newlines
558		let myrepl = substitute(myrepl, '\\n', '\\r', 'g')
559
560	endif
561
562	" save options {{{2
563	" turn off redraw
564	let mylazyredraw = &lazyredraw
565	set lazyredraw
566	" turn off swap
567	let myswapfile = &swapfile
568	set noswapfile
569	" turn off undo
570	let myundolevels = &undolevels
571	set undolevels=-1
572	" ignore autocmds
573	let myeventignore = &eventignore
574	set eventignore=all
575	" unload buffers
576	let myhidden = &hidden
577	set nohidden
578
579	" Edit, Replace, Write, close {{{2
580	"   (the main point!)
581	"
582	" * Force edit/replace/write/close here (we should have already
583	"   picked up errors in validation)
584	" * We do NOT allow regexp at the moment. Thus,
585	"   - magic is turned off
586	"   - substitution uses \V (see :help \V)
587	"   - ALL escaped characters are disallowed, save 2 ("\n" and
588	"     "\t") This is accomplished by escaping all the backslashes
589	"     and then un-escaping just those two.
590
591	" initialize iterations
592	let i = 0
593	" initialize errorlog
594	let myerrorlog = ""
595	" iterate while less than the total file count
596	while i < filecount
597		let i = i + 1
598""*** Don't validate, too slow
599"            " validate
600"            "let myerror = s:Cream_replacemulti_validate(myfile{i})
601"            let myerror = ""
602"            "***
603"            " proceed if empty (valid)
604"            if myerror == ""
605
606			" cmdheight for echo
607			let mycmdheight = &cmdheight
608			set cmdheight=2
609			echo " Progress: " . i . " of " . filecount . " (" . ((i*100) / filecount) . "%)"
610			let &cmdheight = mycmdheight
611
612			" open file
613			execute "silent! edit! " . myfile{i}
614
615			" find/replace ( :s/{pattern}/{string}/{options} )
616			" * {command}
617			"   %  -- globally across the file
618			" * {pattern}
619			"   \V -- eliminates magic (ALL specials must be escaped, most
620			"           of which we thwarted above, MWUHAHAHA!!)
621			" * {string}
622			" * {options}
623			"	g  -- global
624			"	e  -- skip over minor errors (like "not found")
625			"	I  -- don't ignore case
626			"	i  -- ignore case
627
628			" condition based on options
629			let mycmd = ':silent! %s/' . myregexp . myfind . '/' . myrepl . '/ge' . mycase
630
631"*** DEBUG:
632"let mychoice = confirm("DEBUG:\n  mycmd  = " . mycmd, "&Ok\n&Cancel", 1)
633"if mychoice == 1
634"endif
635			" do it!
636			execute mycmd
637"***
638			" save only if modified
639			if &modified == "1"
640				" save file
641				execute "silent! write! " . myfile{i}
642			endif
643
644			" close file (delete from buffer as is our preference ;)
645			execute "silent! bwipeout!"
646
647"            else
648
649"	"*** DEBUG:
650"	"call confirm("DEBUG:\n  Bypassing operation on file:\n\n  " . myfile{i}, "&Ok")
651"	"***
652"                " log error if not valid
653"                let myerrorlog = myerrorlog . myerror
654"            endif
655"*** end validation
656	endwhile
657
658	" restore options {{{2
659
660	" restore
661	let &lazyredraw = mylazyredraw
662	let &swapfile = myswapfile
663	let &undolevels = myundolevels
664	let &eventignore = myeventignore
665	let &hidden = myhidden
666
667	" return magic state
668	if mymagic == "1"
669		set magic
670	else
671		set nomagic
672	endif
673	unlet mymagic
674
675""*** DEBUG
676"" iterate only the first few files
677"let g:bob = 1
678"if g:bob < 3
679"	let msg = "DEBUG: (operation completed)\n"
680"	let msg = msg . "  Find:                 " . myfind . "\n"
681"	let msg = msg . "  \n"
682"	let msg = msg . "  Replace:              " . myrepl . "\n"
683"	let msg = msg . "  \n"
684"	let msg = msg . "  Filename:             " . myfile{i} . "           \n"
685"	let msg = msg . "  \n"
686"	let msg = msg . "  \n"
687"call confirm(msg, "&Ok", 1)
688"endif
689"let g:bob = g:bob + 1
690""***
691
692	if myerrorlog != ""
693		call confirm("Difficulties were encountered during the operation:\n\n" . myerrorlog, "&Ok", "Error")
694	endif
695	" 2}}}
696
697endfunction
698
699" s:Cream_replacemulti_validate() {{{1
700function! s:Cream_replacemulti_validate(filename)
701" Validate a file for editing
702" * Returns empty if valid
703" * Returns string describing error if not valid
704" * Argument must include full path
705
706	" log error in this string (always returned; so if empty, valid)
707	let errorlog = ""
708
709	"......................................................................
710	" do tests
711
712	" is writable?
713	let test1 = filewritable(a:filename)
714
715	" has DOS binary extension?
716	if    has("dos16") ||
717		\ has("dos32") ||
718		\ has("win16") ||
719		\ has("win32")
720
721		let test2 = match(a:filename, "exe", strlen(a:filename) - 3)
722		" if return no match
723		if test2 == '-1'
724			" try 'com'
725			let test2 = match(a:filename, "com", strlen(a:filename) - 3)
726			" if yes, make return code different to distinguish
727			if test2 != '-1'
728				" found, return error code
729				let test2 = '1'
730			else
731				let test2 = '0'
732			endif
733		else
734			" assign error code
735			let test2 = '-1'
736		endif
737	else
738		let test2 = '0'
739	endif
740
741	" file is currently a buffer [distinguish buflisted() and bufloaded()]
742	" * bufexists() returns 0 if doesn't exist, buffer number if does
743	let test3 = bufexists(a:filename)
744
745
746	"......................................................................
747	" log errors (if any)
748	if    test1 == '0' || test1 == '2' ||
749		\ test2 == '1' || test2 == '-1' ||
750		\ test3 != '0'
751
752		" if an error, start with the file name
753		let errorlog = errorlog . "\n" . a:filename . ":\n"
754
755		" filewritable
756		if test1 == '0' || test1 == '2'
757			let errorlog = errorlog . " *  Not writable -- "
758			if test1 == '0'
759				let errorlog = errorlog . "Unable to write file (perhaps read-only or all ready open?)\n"
760			elseif test1 == '2'
761				let errorlog = errorlog . "Filename is a directory\n"
762			endif
763		endif
764
765		" has DOS binary extension
766		if test2 == '1' || test2 == '-1'
767			let errorlog = errorlog . " *   Window program file (binary) -- "
768			if test2 == '-1'
769				let errorlog = errorlog . ".exe file\n"
770			elseif test2 == '1'
771				let errorlog = errorlog . ".com file\n"
772			endif
773		endif
774
775		" is an existing buffer
776		if test3 != '0'
777			let errorlog = errorlog . " *   Buffer currently open "
778		endif
779
780	endif
781
782"*** DEBUG:
783"if errorlog != ""
784"	call confirm("DEBUG:\n  Cream_multireplace_validate(), errorlog:\n" . errorlog, "&Ok")
785"endif
786"***
787
788	return errorlog
789
790endfunction
791
792" 1}}}
793
794" ********************************************************************
795" Localised duplicate Cream functions
796"
797" Functions below are localised versions of global library functions
798" duplicated here for portability of this script outside of the Cream
799" environment.
800" ********************************************************************
801" s:Cream_path_fullsystem() {{{1
802function! s:Cream_path_fullsystem(path, ...)
803" Return a completely expanded and valid path for the current system
804" from string {path}.
805" o Path does not have to exist, although generally invalid formats
806"   will not usually be properly expanded (e.g., backslash path separators
807"   on Unix).
808" o Both files and directories may be processed. (Paths will not be
809"   returned with a trailing path separator.)
810" o Preserves Windows UNC server name preceding "\\".
811" o {optional} argument can be used to override system settings:
812"   * "win" forces return in Windows format as possible:
813"     - No drive letter is added (none can be assumed)
814"   * "unix" forces return to Unix format as possible:
815"     - Windows drive letter is not removed
816"
817
818	" format type
819	" forced with argument
820	if a:0 == 1
821		if     a:1 == "win"
822			let format = "win"
823		elseif a:1 == "unix"
824			let format = "unix"
825		endif
826	endif
827	" detected if not forced
828	if !exists("format")
829		if Cream_has("ms")
830			let format = "win"
831		else
832			let format = "unix"
833		endif
834	endif
835
836	" expand to full path
837	let path = fnamemodify(a:path, ":p")
838
839	" make Windows format (assume is Unix)
840	if format == "win"
841
842		" remove escaping of spaces
843		let path = substitute(path, '\\ ', ' ', "g")
844
845		" convert forward slashes to backslashes
846		let path = substitute(path, '/', '\', "g")
847
848	" make Unix format (assume is Windows)
849	else
850
851		"" strip drive letter
852		"let path = substitute(path, '^\a:', '', '')
853
854		" convert backslashes to forward slashes
855		let path = substitute(path, '\', '/', "g")
856
857		" escape spaces (but not twice)
858		let path = substitute(path, '[/\\]* ', '\\ ', "g")
859
860	endif
861
862	" remove duplicate separators
863	let path = substitute(path, '\\\+', '\', "g")
864	let path = substitute(path, '/\+', '/', "g")
865
866	" remove trailing separators
867	let path = substitute(path, '[/\\]*$', '', "")
868
869	" maintain Windows UNC servername
870	if s:Cream_path_isunc(a:path)
871		let path = substitute(path, '^\', '\\\\', "")
872		let path = substitute(path, '^/', '\\\\', "")
873	endif
874
875	return path
876
877endfunction
878
879" s:Cream_path_isunc() {{{1
880function! s:Cream_path_isunc(path)
881" Returns 1 if {path} is in Windows UNC format, 0 if not.
882	if match(a:path, '^\\\\') != -1 || match(a:path, '^//') != -1
883		return 1
884	endif
885endfunction
886
887" 1}}}
888" vim:foldmethod=marker
889