├── .gitignore ├── README.md ├── doc └── dirdiff.txt ├── plugin └── dirdiff.vim └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | doc/tags 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-dirdiff 2 | 3 | DirDiff plugin for Vim 4 | 5 | If you like Vim's diff mode, you would love to do this recursively on two 6 | directories! 7 | 8 | ![DirDiff Session](/screenshot.png?raw=true "DirDiff Session") 9 | 10 | ## Installation 11 | 12 | With [pathogen.vim](https://github.com/tpope/vim-pathogen): 13 | 14 | cd ~/.vim/bundle 15 | git clone git://github.com/will133/vim-dirdiff 16 | 17 | With [vim-plug](https://github.com/junegunn/vim-plug), in your ~/.vimrc: 18 | 19 | Plug 'will133/vim-dirdiff' 20 | 21 | With Vim 8+'s default packaging system: 22 | 23 | mkdir -p ~/.vim/pack/bundle/start 24 | cd ~/.vim/pack/bundle/start 25 | git clone git://github.com/will133/vim-dirdiff 26 | 27 | 28 | ## Usage 29 | 30 | :DirDiff 31 | 32 | To open DirDiff from the command line, run `vim -c "DirDiff dir1 dir2"` 33 | or add the following function to your shell init file: 34 | 35 | function dirdiff() 36 | { 37 | # Shell-escape each path: 38 | DIR1=$(printf '%q' "$1"); shift 39 | DIR2=$(printf '%q' "$1"); shift 40 | vim $@ -c "DirDiff $DIR1 $DIR2" 41 | } 42 | 43 | If you use pathogen, you can use :Helptags to regenerate documentation. You 44 | then can see ":h dirdiff" for more information. 45 | 46 | 47 | ## License 48 | 49 | Copyright (c) 2001-2015 William Lee. (BSD-Like) See doc/dirdiff.txt. 50 | -------------------------------------------------------------------------------- /doc/dirdiff.txt: -------------------------------------------------------------------------------- 1 | *dirdiff.txt* Diff and merge two directories recursively 2 | 3 | Author: William Lee 4 | License: BSD-like (see |dirdiff-license|) 5 | 6 | INTRODUCTION *dirdiff* *DirDiff* 7 | 8 | This plugin enables you to run vim-diff on two directories recursively. You 9 | can also perform batch or individual merge through a simple interface. 10 | 11 | REQUIREMENTS *dirdiff-requirements* 12 | 13 | - Make sure you have GNU diff in your path on Unix and Windows. I only 14 | tested this on cygwin's version on Windows. If you have a diff that 15 | doesn't support -x or -I flag, do not set variable g:DirDiffExcludes and 16 | g:DirDiffIgnore to "". It should still work. 17 | - On Windows, you need to have "xcopy", "copy", "del" "rd" and "xxd" in your 18 | path. 19 | - On Unix, you need to have "rm", "cp" and "xxd" in your path. 20 | 21 | INSTALLATION *dirdiff-installation* 22 | 23 | With pathogen.vim: 24 | 25 | $ cd ~/.vim/bundle 26 | $ git clone git://github.com/will133/vim-dirdiff 27 | 28 | With vim-plug, put this in your ~/.vimrc: 29 | 30 | Plug 'will133/vim-dirdiff' 31 | 32 | With Vim 8+'s default packaging system, run this: 33 | 34 | $ mkdir -p ~/.vim/pack/bundle/start 35 | $ cd ~/.vim/pack/bundle/start 36 | $ git clone git://github.com/will133/vim-dirdiff 37 | 38 | 39 | USAGE *dirdiff-usage* *:DirDiff* 40 | > 41 | :DirDiff 42 | < 43 | 44 | For example: 45 | > 46 | :DirDiff ../something/dir1 /usr/bin/somethingelse/dir2 47 | < 48 | 49 | MAPS *dirdiff-maps* 50 | 51 | The following commands can be used inside the diff window: 52 | 53 | *dirdiff-enter* *dirdiff-o* 54 | , Diff open: open the diff file(s) where your cursor is 55 | at 56 | 57 | *dirdiff-s* 58 | Synchronize the current diff. You can also select a 59 | range (through visual) and press 's' to synchronize 60 | differences across a range. 61 | 62 | There are 6 Options you can choose when you hit : 63 | 64 | 1. A -> B 65 | Copy A to overwrite B 66 | If A's file actually points to a directory, it'll 67 | copy it to B recursively. 68 | 2. B -> A 69 | Copy B to overwrite A 70 | If B's file actually points to a directory, it'll 71 | copy it to A recursively. 72 | 3. Always A 73 | For the rest of the items that you've selected, 74 | synchronize like (1). 75 | 4. Always B 76 | For the rest of the items that you've selected, 77 | synchronize like (2). 78 | 5. Skip 79 | Skip this diff entry. 80 | 6. Cancel 81 | Quit the loop and exit. 82 | 83 | *dirdiff-u* 84 | Diff update: update the diff window 85 | 86 | *dirdiff-x* 87 | Sets the exclude pattern, separated by ',' 88 | 89 | *dirdiff-i* 90 | Sets the ignore pattern, separated by ',' 91 | 92 | *dirdiff-a* 93 | Sets additional arguments for diff, eg. -w to ignore 94 | white space, etc. 95 | 96 | *dirdiff-h* 97 | Toggle xxd hex mode on or off. 98 | 99 | *dirdiff-w* 100 | Toggle wrap and nowrap mode 101 | 102 | Quit DirDiff 103 | 104 | The following comamnds can be used in the Vim diff mode if this is global flag 105 | is set to 1 (default to 0): > 106 | 107 | let g:DirDiffEnableMappings = 1 108 | < 109 | 110 | Note: by default these would not be added, unlike previous version. It is 111 | preferable to not change people's mappings by default. These mappings can be 112 | overriden by their corresponding variables added to .vimrc. 113 | 114 | *dirdiff-leader-dg* 115 | dg Diff get: maps to :diffget 116 | > 117 | let g:DirDiffGetKeyMap = 'dg' 118 | < 119 | *dirdiff-leader-dp* 120 | dp Diff put: maps to :diffput 121 | > 122 | let g:DirDiffPutKeyMap = 'dp' 123 | < 124 | *dirdiff-leader-dj* 125 | dj Diff next: (think j for down) 126 | > 127 | let g:DirDiffNextKeyMap = 'dj' 128 | < 129 | 130 | *dirdiff-leader-dk* 131 | dk Diff previous: (think k for up) 132 | > 133 | let g:DirDiffPrevKeyMap = 'dk' 134 | < 135 | 136 | 137 | OPTIONS *dirdiff-options* 138 | 139 | You can add the following "let" lines in your .vimrc file in order to customize 140 | the plugin's behavior. 141 | 142 | Enable additional mappings in diff mode (set to 1 to enable mappings). 143 | Default to do nothing: > 144 | 145 | let g:DirDiffEnableMappings = 0 146 | < 147 | Ignore FileName case during diff: > 148 | 149 | let g:DirDiffIgnoreFileNameCase = 0 150 | 151 | Sets default exclude pattern: > 152 | 153 | let g:DirDiffExcludes = "CVS,*.class,*.exe,.*.swp" 154 | 155 | Sets default ignore pattern: > 156 | 157 | let g:DirDiffIgnore = "Id:,Revision:,Date:" 158 | 159 | If DirDiffSort is set to 1, sorts the diff lines: > 160 | 161 | let g:DirDiffSort = 1 162 | 163 | Sets the diff window (bottom window) height (rows): > 164 | 165 | let g:DirDiffWindowSize = 14 166 | 167 | Ignore case during diff: > 168 | 169 | let g:DirDiffIgnoreCase = 0 170 | 171 | Force setting the LANG variable before running DirDiff: > 172 | 173 | let g:DirDiffForceLang = "C" 174 | 175 | You can set g:DirDiffForceLang to "" in turn this off (the system's LANG would 176 | be used in such case). You may need to customize the following variables so 177 | DirDiff can match the correct text in your locale. 178 | 179 | Force the diff execution shell: > 180 | 181 | let g:DirDiffForceShell = "C" 182 | 183 | If empty, the default shell would not be changed before running the diff 184 | command. If not, then the shell would be set to this before running the diff 185 | command (and restored after the execuation). This is useful for people who 186 | are using shells that are not compatible. If you are getting a message saying 187 | that there's no diff for some shell, you may want to set this to "sh". 188 | 189 | Dynamically figure out the diff text. If you are using and i18n version of 190 | diff, this will try to get the specific diff text during runtime. It's turned 191 | off by default. If you are always targetting a specific version of diff, you 192 | can turn this off and set the DirDiffText* variables accordingly: > 193 | 194 | let g:DirDiffDynamicDiffText = 0 195 | 196 | String used for the English equivalent "Files ": > 197 | 198 | let g:DirDiffTextFiles = "Files " 199 | 200 | String used for the English equivalent " and ": > 201 | 202 | let g:DirDiffTextAnd = " and " 203 | 204 | String used for the English equivalent " differ"): > 205 | 206 | let g:DirDiffTextDiffer = " differ" 207 | 208 | String used for the English equivalent "Only in "): > 209 | 210 | let g:DirDiffTextOnlyIn = "Only in " 211 | 212 | To specify a valid theme (e.g. github): > 213 | 214 | let g:DirDiffTheme = "github" 215 | 216 | To use [ and ] rather than [c and ]c motions: > 217 | 218 | let g:DirDiffSimpleMap = 1 219 | 220 | Add extra options to the "diff" tool. For example, use "-w" to ignore white 221 | spaces or "-N" to list all new files even when inside a new folder (instead of 222 | just listing the new folder name) 223 | 224 | let g:DirDiffAddArgs = "-w" 225 | 226 | OPTIONS EXAMPLE *dirdiff-options-example* 227 | 228 | This is an example for setting DirDiffExcludes and DirDiffIgnore in addition 229 | to enabling the plugin's mappings. Note that patterns are separated with 230 | commas and no spaces. 231 | 232 | For example, you can set these in your .vimrc file: > 233 | 234 | let g:DirDiffExcludes = "CVS,*.class,*.o" 235 | let g:DirDiffIgnore = "Id:" 236 | " ignore white space in diff 237 | let g:DirDiffAddArgs = "-w" 238 | let g:DirDiffEnableMappings = 1 239 | < 240 | 241 | WARNING *dirdiff-warning* 242 | 243 | This script can copy and remove your files. This can be powerful (or too 244 | powerful) at times. Please be careful and use version control! 245 | 246 | LICENSE *dirdiff-license* 247 | 248 | Copyright (c) 2001-2015 William Lee. 249 | 250 | Redistribution and use in source and binary forms, with or without 251 | modification, are permitted provided that the following conditions are 252 | met: 253 | 254 | * Redistributions of source code must retain the above copyright 255 | notice, this list of conditions and the following disclaimer. 256 | * Redistributions in binary form must reproduce the above copyright 257 | notice, this list of conditions and the following disclaimer in the 258 | documentation and/or other materials provided with the distribution. 259 | * Neither the name William Lee nor the names of its contributors may be 260 | used to endorse or promote products derived from this software without 261 | specific prior written permission. 262 | 263 | THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 264 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 265 | AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 266 | WILLIAM LEE AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 267 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 268 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 269 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 270 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 271 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 272 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 273 | 274 | THANKS *dirdiff-thanks* 275 | 276 | Florian Delizy for the i18n diff patch 277 | Robert Webb for his sorting function 278 | Wu WeiWei for his Chinese diff patch 279 | Salman Halim, Yosuke Kimura, and others for their suggestions 280 | 281 | HISTORY *dirdiff-history* 282 | 283 | 1.1.7 - Added support for "hex mode" and "wrap mode" 284 | 1.1.6 - Fixed problem with vim 8.2 where the :drop would not work with 285 | wildignore. Added option to set shell. 286 | 1.1.5 - Fixed split windows problems caused by some .vimrc settings. 287 | 1.1.4 - Fixed split windows problems caused by some .vimrc settings. 288 | 1.1.3 - Applied the patch to 1.1.2 by Wu WeiWei in order to make diff 289 | that's localized in Chinese work. 290 | 1.1.2 - Applied the patch to 1.1.0 instead of 1.0.2. Please do not use 291 | 1.1.1 292 | 1.1.1 - Make it work with filename with spaces. (Thanks to Atte Kojo) 293 | 1.1.0 - Added support for i18n (tested on a French version for now only). 294 | Can dynamically figure out the diff strings output by diff. 295 | 1.0.2 - Fixed a small typo bug in the quit function. 296 | 1.0.1 - Ensure the path separator is correct when running in W2K 297 | 1.0 - Fixed a bug that flags errors if the user use the nowrapscan option. 298 | Implements a quit function that exit the diff windows. 299 | 0.94 - Fixed a bug where the diff will give incorrect A and B file due to 300 | similarity of directory names. Allow you to modify the diff 301 | argument. 302 | 0.93 - Opps, messed up the key mapping usage. 303 | 0.92 - Doesn't use n and p mappings since it confuses the search next key 304 | mapping and causes some bugs. Minor modification to change the 305 | exclude and ignore pattern. 306 | 0.91 - Clean up delete routine. 307 | - Added interactive mode. 308 | - Added multiple entries of exclude and ignore pattern. 309 | - Custom configuration through global variables. 310 | - Change exclude and ignore patterns on the fly. 311 | 312 | 0.9 - Reorganization of the interface. Much simplier dialog for 313 | synchronization. Support for range synchronization option (REALLY 314 | powerful) 315 | - Removed unnecessary key bindings. All key bindings are local to 316 | the diff window. (except for the \dg and \dp) 317 | 318 | 0.8 - Added syntax highlighting. 319 | - Enter and double-click in buffer opens diff. 320 | - Removed dependency on "sort" 321 | - Removed usage of registry and marker 322 | - Code cleanup and some bug fixes 323 | - On Windows the diff command will use the -i flag instead 324 | - Changed mappings for diff next (\dj) and diff previous (\dk) 325 | - Added mappings for vim diff mode (\dg, \dp) 326 | 327 | 0.7 Initial Release 328 | 329 | vim:tw=78:et:ft=help:norl: 330 | -------------------------------------------------------------------------------- /plugin/dirdiff.vim: -------------------------------------------------------------------------------- 1 | " -*- vim -*- 2 | " (C) 2001-2020 by William Lee, 3 | " }}} 4 | 5 | if exists('g:loaded_dirdiff') 6 | finish 7 | endif 8 | let g:loaded_dirdiff = 1 9 | 10 | " Public Interface: 11 | command! -nargs=* -complete=dir DirDiff call DirDiff () 12 | command! -nargs=0 DirDiffOpen call DirDiffOpen () 13 | command! -nargs=0 DirDiffNext call DirDiffNext () 14 | command! -nargs=0 DirDiffPrev call DirDiffPrev () 15 | command! -nargs=0 DirDiffUpdate call DirDiffUpdate () 16 | command! -nargs=0 DirDiffQuit call DirDiffQuit () 17 | 18 | " The following comamnds can be used in the Vim diff mode: 19 | " 20 | " \dg - Diff get: maps to :diffget 21 | " \dp - Diff put: maps to :diffput 22 | " \dj - Diff next: (think j for down) 23 | " \dk - Diff previous: (think k for up) 24 | 25 | if get(g:, 'DirDiffEnableMappings', 0) 26 | silent! exe 'nnoremap ' . get(g:, 'DirDiffGetKeyMap', 'dg') . ' :diffget' 27 | silent! exe 'nnoremap ' . get(g:, 'DirDiffPutKeyMap', 'dp') . ' :diffput' 28 | silent! exe 'nnoremap ' . get(g:, 'DirDiffNextKeyMap', 'dj') . ' :DirDiffNext' 29 | silent! exe 'nnoremap ' . get(g:, 'DirDiffPrevKeyMap', 'dk') . ' :DirDiffPrev' 30 | endif 31 | 32 | " Global Maps: 33 | 34 | " Default Variables. You can override these in your global variables 35 | " settings. 36 | " 37 | " For DirDiffExcludes and DirDiffIgnore, separate different patterns with a 38 | " ',' (comma and no space!). 39 | " 40 | " eg. in your .vimrc file: let g:DirDiffExcludes = "CVS,*.class,*.o" 41 | " let g:DirDiffIgnore = "Id:" 42 | " " ignore white space in diff 43 | " let g:DirDiffAddArgs = "-w" 44 | " 45 | " You can set the pattern that diff excludes. Defaults to the CVS directory 46 | if !exists("g:DirDiffExcludes") 47 | let g:DirDiffExcludes = "" 48 | endif 49 | " This is the -I argument of the diff, ignore the lines of differences that 50 | " matches the pattern 51 | if !exists("g:DirDiffIgnore") 52 | let g:DirDiffIgnore = "" 53 | endif 54 | if !exists("g:DirDiffSort") 55 | let g:DirDiffSort = 1 56 | endif 57 | if !exists("g:DirDiffWindowSize") 58 | let g:DirDiffWindowSize = 14 59 | endif 60 | if !exists("g:DirDiffInteractive") 61 | let g:DirDiffInteractive = 0 62 | endif 63 | if !exists("g:DirDiffIgnoreCase") 64 | let g:DirDiffIgnoreCase = 0 65 | endif 66 | if !exists("g:DirDiffTheme") 67 | let g:DirDiffTheme = "" 68 | endif 69 | if !exists("g:DirDiffSimpleMap") 70 | let g:DirDiffSimpleMap = 0 71 | endif 72 | " Additional arguments 73 | if !exists("g:DirDiffAddArgs") 74 | let g:DirDiffAddArgs = "" 75 | endif 76 | " Support for i18n (dynamically figure out the diff text) 77 | " Defaults to off 78 | if !exists("g:DirDiffDynamicDiffText") 79 | let g:DirDiffDynamicDiffText = 0 80 | endif 81 | 82 | if !exists("g:DirDiffIgnoreFileNameCase") 83 | let g:DirDiffIgnoreFileNameCase = 0 84 | endif 85 | 86 | " Force set the LANG variable before running the C command. Default to C. 87 | " Set to "" to not set the variable. 88 | if !exists("g:DirDiffForceLang") 89 | let g:DirDiffForceLang = "C" 90 | endif 91 | 92 | " Force the shell to run the diff command to be this. If set to an empty 93 | " string, the shell would not be changed. 94 | if !exists("g:DirDiffForceShell") 95 | let g:DirDiffForceShell = "" 96 | endif 97 | 98 | let g:DirDiffLangString = "" 99 | if (g:DirDiffForceLang != "") 100 | if has('win32') && !has('win32unix') 101 | let g:DirDiffLangString = 'SET LANG=' . g:DirDiffForceLang . ' && ' 102 | else 103 | let g:DirDiffLangString = 'LANG=' . g:DirDiffForceLang . ' ' 104 | endif 105 | endif 106 | 107 | " String used for the English equivalent "Files " 108 | if !exists("g:DirDiffTextFiles") 109 | let g:DirDiffTextFiles = "Files " 110 | endif 111 | 112 | " String used for the English equivalent " and " 113 | if !exists("g:DirDiffTextAnd") 114 | let g:DirDiffTextAnd = " and " 115 | endif 116 | 117 | " String used for the English equivalent " differ") 118 | if !exists("g:DirDiffTextDiffer") 119 | let g:DirDiffTextDiffer = " differ" 120 | endif 121 | 122 | " String used for the English equivalent "Only in ") 123 | if !exists("g:DirDiffTextOnlyIn") 124 | let g:DirDiffTextOnlyIn = "Only in " 125 | endif 126 | 127 | " String used for the English equivalent ": ") 128 | if !exists("g:DirDiffTextOnlyInCenter") 129 | let g:DirDiffTextOnlyInCenter = ": " 130 | endif 131 | 132 | " Set some script specific variables: 133 | " 134 | let s:DirDiffFirstDiffLine = 6 135 | let s:DirDiffALine = 1 136 | let s:DirDiffBLine = 2 137 | 138 | " -- Variables used in various utilities 139 | if has("unix") 140 | let s:DirDiffCopyCmd = "cp" 141 | let s:DirDiffCopyFlags = "" 142 | let s:DirDiffCopyDirCmd = "cp" 143 | let s:DirDiffCopyDirFlags = "-rf" 144 | let s:DirDiffCopyInteractiveFlag = "-i" 145 | 146 | let s:DirDiffDeleteCmd = "rm" 147 | let s:DirDiffDeleteFlags = "" 148 | let s:DirDiffDeleteInteractiveFlag = "-i" 149 | 150 | let s:DirDiffDeleteDirCmd = "rm" 151 | let s:DirDiffDeleteDirFlags = "-rf" 152 | 153 | let s:sep = "/" 154 | 155 | let s:DirDiffMakeDirCmd = "!mkdir " 156 | 157 | elseif has("win32") 158 | let s:DirDiffCopyCmd = "copy" 159 | let s:DirDiffCopyFlags = "" 160 | let s:DirDiffCopyDirCmd = "xcopy" 161 | let s:DirDiffCopyDirFlags = "/e /i /q" 162 | let s:DirDiffCopyInteractiveFlag = "/-y" 163 | 164 | let s:DirDiffDeleteCmd = "del" 165 | let s:DirDiffDeleteFlags = "/s /q" 166 | let s:DirDiffDeleteInteractiveFlag = "/p" 167 | " Windows is somewhat stupid since "del" can only remove the files, not 168 | " the directory. The command "rd" would remove files recursively, but it 169 | " doesn't really work on a file (!). where is the deltree command??? 170 | 171 | let s:DirDiffDeleteDirCmd = "rd" 172 | " rd is by default prompting, we need to handle this in a different way 173 | let s:DirDiffDeleteDirFlags = "/s" 174 | let s:DirDiffDeleteDirQuietFlag = "/q" 175 | 176 | let s:sep = "\\" 177 | 178 | let s:DirDiffMakeDirCmd = "!mkdir " 179 | else 180 | " Platforms not supported 181 | let s:DirDiffCopyCmd = "" 182 | let s:DirDiffCopyFlags = "" 183 | let s:DirDiffDeleteCmd = "" 184 | let s:DirDiffDeleteFlags = "" 185 | let s:sep = "" 186 | endif 187 | 188 | 189 | function! DirDiff(srcA, srcB) 190 | " Setup 191 | let DirDiffAbsSrcA = fnamemodify(expand(a:srcA, ":p"), ":p") 192 | let DirDiffAbsSrcB = fnamemodify(expand(a:srcB, ":p"), ":p") 193 | " Check for an internationalized version of diff ? 194 | call GetDiffStrings() 195 | 196 | " Remove the trailing \ or / 197 | let DirDiffAbsSrcA = substitute(DirDiffAbsSrcA, '\\$\|/$', '', '') 198 | let DirDiffAbsSrcB = substitute(DirDiffAbsSrcB, '\\$\|/$', '', '') 199 | 200 | let s:FilenameDiffWindow = tempname() 201 | " We first write to that file 202 | " Constructs the command line 203 | let langStr = "" 204 | let cmd = "!" . g:DirDiffLangString . "diff" 205 | let cmdarg = " -r --brief" 206 | 207 | if (g:DirDiffIgnoreFileNameCase) 208 | let cmdarg = cmdarg." --ignore-file-name-case" 209 | endif 210 | 211 | " If variable is set, we ignore the case 212 | if (g:DirDiffIgnoreCase) 213 | let cmdarg = cmdarg." -i" 214 | endif 215 | if (g:DirDiffAddArgs != "") 216 | let cmdarg = cmdarg." ".g:DirDiffAddArgs." " 217 | endif 218 | if (g:DirDiffExcludes != "") 219 | let cmdarg = cmdarg.' -x"'.substitute(g:DirDiffExcludes, ',', '" -x"', 'g').'"' 220 | endif 221 | if (g:DirDiffIgnore != "") 222 | let cmdarg = cmdarg.' -I"'.substitute(g:DirDiffIgnore, ',', '" -I"', 'g').'"' 223 | endif 224 | " Prompt the user for additional arguments 225 | " let addarg = input("Additional diff args (current =". cmdarg. "): ") 226 | let addarg = "" 227 | let cmd = cmd.cmdarg." ".addarg." \"".DirDiffAbsSrcA."\" \"".DirDiffAbsSrcB."\"" 228 | let cmd = cmd." > \"".s:FilenameDiffWindow."\"" 229 | 230 | echo "Diffing directories, it may take a while..." 231 | let error = DirDiffExec(cmd, 0) 232 | if (error == 0) 233 | redraw | echom "diff found no differences - directories match." 234 | return 235 | endif 236 | silent exe "edit ".s:FilenameDiffWindow 237 | echo "Defining [A] and [B] ... " 238 | " We then do a substitution on the directory path 239 | " We need to do substitution of the the LONGER string first, otherwise 240 | " it'll mix up the A and B directory 241 | if (strlen(DirDiffAbsSrcA) > strlen(DirDiffAbsSrcB)) 242 | silent! exe "%s/".EscapeDirForRegex(DirDiffAbsSrcA)."/[A]/" 243 | silent! exe "%s/".EscapeDirForRegex(DirDiffAbsSrcB)."/[B]/" 244 | else 245 | silent! exe "%s/".EscapeDirForRegex(DirDiffAbsSrcB)."/[B]/" 246 | silent! exe "%s/".EscapeDirForRegex(DirDiffAbsSrcA)."/[A]/" 247 | endif 248 | " In windows, diff behaves somewhat weirdly, for the appened path it'll 249 | " use "/" instead of "\". Convert this to \ 250 | if (has("win32")) 251 | silent! %s/\//\\/g 252 | endif 253 | 254 | echo "Sorting entries ..." 255 | " We then sort the lines if the option is set 256 | if (g:DirDiffSort == 1) 257 | 1,$call Sort("s:Strcmp") 258 | endif 259 | 260 | " Put in spacer in front of each line 261 | silent! %s/^/ / 262 | 263 | " We then put the file [A] and [B] on top of the diff lines 264 | call append(0, "[A]=". DirDiffAbsSrcA) 265 | call append(1, "[B]=". DirDiffAbsSrcB) 266 | if get(g:, 'DirDiffEnableMappings', 0) 267 | call append(2, "Usage: /'o'=open,'s'=sync,'dg'=diffget,'dp'=diffput,'dj'=next,'dk'=prev, 'q'=quit") 268 | else 269 | call append(2, "Usage: /'o'=open,'s'=sync,'q'=quit") 270 | endif 271 | call append(3, "Options: 'u'=update,'x'=set excludes,'i'=set ignore,'a'=set args, 'h'=hex mode, 'w'=wrap mode") 272 | call append(4, "Diff Args:" . cmdarg) 273 | call append(5, "") 274 | " go to the beginning of the file 275 | 0 276 | setlocal filetype=dirdiff 277 | setlocal nomodified 278 | setlocal nomodifiable 279 | setlocal buftype=nowrite 280 | "setlocal buftype=nofile 281 | "setlocal bufhidden=delete 282 | setlocal bufhidden=hide 283 | setlocal nowrap 284 | 285 | " Set up local key bindings 286 | " 'n' actually messes with the search next pattern, I think using \dj and 287 | " \dk is enough. Otherwise, use j,k, and enter. 288 | " nnoremap n :call DirDiffNext() 289 | " nnoremap p :call DirDiffPrev() 290 | nnoremap s :. call DirDiffSync() 291 | vnoremap s :call DirDiffSync() 292 | nnoremap u :call DirDiffUpdate() 293 | nnoremap x :call ChangeExcludes() 294 | nnoremap a :call ChangeArguments() 295 | nnoremap i :call ChangeIgnore() 296 | nnoremap h :call DirDiffHexmode() 297 | nnoremap w :call DirDiffWrapmode() 298 | nnoremap q :call DirDiffQuit() 299 | 300 | nnoremap o :call DirDiffOpen() 301 | nnoremap :call DirDiffOpen() 302 | nnoremap <2-Leftmouse> :call DirDiffOpen() 303 | call SetupSyntax() 304 | 305 | " Open the first diff 306 | call DirDiffNext() 307 | endfunction 308 | 309 | " Set up syntax highlighing for the diff window 310 | "function! SetupSyntax() 311 | function! SetupSyntax() 312 | if has("syntax") && exists("g:syntax_on") 313 | "&& !has("syntax_items") 314 | syn match DirDiffSrcA "\[A\]" 315 | syn match DirDiffSrcB "\[B\]" 316 | syn match DirDiffUsage "^Usage.*" 317 | syn match DirDiffOptions "^Options.*" 318 | " exec 'syn match DirDiffFiles "' . s:DirDiffDifferLine .'"' 319 | " exec 'syn match DirDiffOnly "' . s:DirDiffDiffOnlyLine . '"' 320 | syn match DirDiffSelected "^==>.*" contains=DirDiffSrcA,DirDiffSrcB 321 | 322 | hi def link DirDiffSrcA Directory 323 | hi def link DirDiffSrcB Type 324 | hi def link DirDiffUsage Special 325 | hi def link DirDiffOptions Special 326 | hi def link DirDiffFiles String 327 | hi def link DirDiffOnly PreProc 328 | hi def link DirDiffSelected DiffChange 329 | endif 330 | endfunction 331 | 332 | " You should call this within the diff window 333 | function! DirDiffUpdate() 334 | let dirA = GetBaseDir("A") 335 | let dirB = GetBaseDir("B") 336 | call DirDiff(dirA, dirB) 337 | endfun 338 | 339 | " Quit the DirDiff mode 340 | function! DirDiffQuit() 341 | let in = confirm ("Are you sure you want to quit DirDiff?", "&Yes\n&No", 2) 342 | if (in == 1) 343 | call SaveDiffWindowsIfModified() 344 | bd! 345 | endif 346 | unlet! s:FilenameDiffWindow 347 | unlet! s:FilenameA 348 | unlet! s:FilenameB 349 | unlet! s:LastMode 350 | endfun 351 | 352 | " Returns an escaped version of the path for regex uses 353 | function! EscapeDirForRegex(path) 354 | " This list is probably not complete, modify later 355 | return escape(a:path, "/\\[]$^~") 356 | endfunction 357 | 358 | " Closes a specified window 359 | function! AskToSaveFileIfModified(file) 360 | let bufNum = bufnr(a:file) 361 | if !bufexists(bufNum) 362 | return 363 | endif 364 | 365 | call SaveIfModified(bufNum) 366 | endfunction 367 | 368 | " Close the opened diff comparison windows if they exist 369 | function! SaveDiffWindowsIfModified() 370 | if exists("s:FilenameA") 371 | call AskToSaveFileIfModified(s:FilenameA) 372 | endif 373 | if exists("s:FilenameA") 374 | call AskToSaveFileIfModified(s:FilenameB) 375 | endif 376 | endfunction 377 | 378 | " Toggle hexmode from http://vim.wikia.com/wiki/Hex 379 | function ToggleHex() 380 | " hex mode should be considered a read-only operation 381 | " save values for modified and read-only for restoration later, 382 | " and clear the read-only flag for now 383 | let l:modified=&mod 384 | let l:oldreadonly=&readonly 385 | let &readonly=0 386 | let l:oldmodifiable=&modifiable 387 | let &modifiable=1 388 | if !exists("b:editHex") || !b:editHex 389 | " save old options 390 | let b:oldft=&ft 391 | let b:oldbin=&bin 392 | " set new options 393 | setlocal binary " make sure it overrides any textwidth, etc. 394 | let &ft="xxd" 395 | " set status 396 | let b:editHex=1 397 | " switch to hex editor 398 | silent %!xxd 399 | else 400 | " restore old options 401 | let &ft=b:oldft 402 | if !b:oldbin 403 | setlocal nobinary 404 | endif 405 | " set status 406 | let b:editHex=0 407 | " return to normal editing 408 | silent %!xxd -r 409 | endif 410 | " restore values for modified and read only state 411 | let &mod=l:modified 412 | let &readonly=l:oldreadonly 413 | let &modifiable=l:oldmodifiable 414 | endfunction 415 | 416 | function! DirDiffHexmode() 417 | wincmd k 418 | call ToggleHex() 419 | wincmd l 420 | call ToggleHex() 421 | " Go back to the diff window 422 | wincmd j 423 | endfunction 424 | 425 | function! DirDiffWrapmode() 426 | wincmd k 427 | setlocal wrap! 428 | wincmd l 429 | setlocal wrap! 430 | " Go back to the diff window 431 | wincmd j 432 | endfunction 433 | 434 | function! EscapeFileName(path) 435 | if (v:version >= 702) 436 | return fnameescape(a:path) 437 | else 438 | " This is not a complete list of escaped character, so it's 439 | " not as sophisicated as the fnameescape, but this should 440 | " cover most of the cases and should work for Vim version < 441 | " 7.2 442 | return escape(a:path, " \t\n*?[{`$\\%#'\"|!<") 443 | endif 444 | endfunction 445 | 446 | function! EchoErr(varName, varValue) 447 | echoe '' . a:varName . ' : ' . a:varValue 448 | endfunction 449 | 450 | function! Drop(fname) 451 | " We need to replace the :drop implementation due to this issue: 452 | " https://github.com/vim/vim/issues/1503. Thus if wideignore is set the 453 | " command would no work. This is intended to work around that issue 454 | let winid = bufwinid(a:fname) 455 | if winid > 0 456 | call win_gotoid(winid) 457 | else 458 | exe 'edit ' a:fname 459 | endif 460 | endfunction 461 | 462 | function! GotoDiffWindow() 463 | call Drop(s:FilenameDiffWindow) 464 | endfunction 465 | 466 | function! DirDiffOpen() 467 | " Ensure we're in the right window 468 | call GotoDiffWindow() 469 | if exists("b:currentDiff") && b:currentDiff == line(".") 470 | return 471 | endif 472 | 473 | " First dehighlight the last marked line 474 | call DeHighlightLine() 475 | 476 | " Close existing diff windows 477 | call SaveDiffWindowsIfModified() 478 | 479 | " Change back to the right window 480 | call GotoDiffWindow() 481 | 482 | let line = getline(".") 483 | let b:currentDiff = line(".") 484 | 485 | let previousFileA = exists("s:FilenameA") ? s:FilenameA : "" 486 | let previousFileB = exists("s:FilenameB") ? s:FilenameB : "" 487 | 488 | " Parse the line and see whether it's a "Only in" or "Files Differ" 489 | call HighlightLine() 490 | let fileA = GetFileNameFromLine("A", line) 491 | let fileB = GetFileNameFromLine("B", line) 492 | let s:FilenameA = EscapeFileName(fileA) 493 | let s:FilenameB = EscapeFileName(fileB) 494 | 495 | if IsOnly(line) 496 | " We open the file 497 | let fileSrc = ParseOnlySrc(line) 498 | if (fileSrc == "A") 499 | let fileToOpen = s:FilenameA 500 | elseif (fileSrc == "B") 501 | let fileToOpen = s:FilenameB 502 | endif 503 | 504 | if exists("s:LastMode") 505 | if s:LastMode == 2 506 | silent exec "bd ".bufnr(previousFileA) 507 | 508 | call Drop(previousFileB) 509 | silent exec "edit ".fileToOpen 510 | else 511 | let previousFile = (s:LastMode == "A") ? previousFileA : previousFileB 512 | call Drop(previousFile) 513 | silent exec "edit ".fileToOpen 514 | silent exec "bd ".bufnr(previousFile) 515 | endif 516 | else 517 | silent exec "split ".fileToOpen 518 | endif 519 | 520 | " Fool the window saying that this is diff 521 | diffthis 522 | call GotoDiffWindow() 523 | " Resize the window 524 | exe("resize " . g:DirDiffWindowSize) 525 | exe (b:currentDiff) 526 | let s:LastMode = fileSrc 527 | elseif IsDiffer(line) 528 | 529 | if exists("s:LastMode") 530 | if s:LastMode == 2 531 | call Drop(previousFileA) 532 | silent exec "edit ".s:FilenameA 533 | diffthis 534 | silent exec "bd ".bufnr(previousFileA) 535 | 536 | call Drop(previousFileB) 537 | silent exec "edit ".s:FilenameB 538 | diffthis 539 | silent exec "bd ".bufnr(previousFileB) 540 | else 541 | let previousFile = (s:LastMode == "A") ? previousFileA : previousFileB 542 | call Drop(previousFile) 543 | silent exec "edit ".s:FilenameB 544 | silent exec "bd ".bufnr(previousFile) 545 | diffthis 546 | 547 | " To ensure that A is on the left and B on the right, splitright must be off 548 | silent exec "leftabove vert diffsplit ".s:FilenameA 549 | endif 550 | else 551 | "Open the diff windows 552 | silent exec "split ".s:FilenameB 553 | 554 | " To ensure that A is on the left and B on the right, splitright must be off 555 | silent exec "leftabove vert diffsplit ".s:FilenameA 556 | endif 557 | 558 | " Go back to the diff window 559 | call GotoDiffWindow() 560 | " Resize the window 561 | exe("resize " . g:DirDiffWindowSize) 562 | exe (b:currentDiff) 563 | " Center the line 564 | exe ("normal z.") 565 | let s:LastMode = 2 566 | else 567 | echo "There is no diff at the current line!" 568 | endif 569 | endfunction 570 | 571 | " Ask the user to save if the buffer is modified 572 | " 573 | function! SaveIfModified(bufNum) 574 | if &autowriteall 575 | return 576 | endif 577 | 578 | if (getbufvar(a:bufNum, "&modified")) 579 | let name = bufname(a:bufNum) 580 | let fullName = fnamemodify(name, ":p") 581 | let input = confirm("File " . fullName . " has been modified.", "&Save\nCa&ncel", 1) 582 | if (input == 1) 583 | cal Drop(name) 584 | exec "w! ".name 585 | endif 586 | endif 587 | endfunction 588 | 589 | " Highlights the selected line in the diff window 590 | " 591 | function! HighlightLine() 592 | let savedLine = line(".") 593 | exe (b:currentDiff) 594 | setlocal modifiable 595 | let line = getline(".") 596 | if (match(line, "^ ") == 0) 597 | let line = substitute(line, "^ ", "==> ", "") 598 | call setline(savedLine, line) 599 | endif 600 | setlocal nomodifiable 601 | setlocal nomodified 602 | exe (savedLine) 603 | " This is necessary since the modified file would make the syntax 604 | " disappear. 605 | call SetupSyntax() 606 | redraw 607 | endfunction 608 | 609 | " Removes the highlight from the selected line in the diff window 610 | " 611 | function! DeHighlightLine() 612 | let savedLine = line(".") 613 | exe (b:currentDiff) 614 | let line = getline(".") 615 | setlocal modifiable 616 | if (match(line, "^==> ") == 0) 617 | let line = substitute(line, "^==> ", " ", "") 618 | call setline(line("."), line) 619 | endif 620 | setlocal nomodifiable 621 | setlocal nomodified 622 | exe (savedLine) 623 | redraw 624 | endfunction 625 | 626 | " Returns the directory for buffer "A" or "B". You need to be in the diff 627 | " buffer though. 628 | function! GetBaseDir(diffName) 629 | let currLine = line(".") 630 | if (a:diffName == "A") 631 | let baseLine = s:DirDiffALine 632 | else 633 | let baseLine = s:DirDiffBLine 634 | endif 635 | let regex = '\['.a:diffName.'\]=\(.*\)' 636 | let line = getline(baseLine) 637 | let rtn = substitute(line, regex , '\1', '') 638 | return rtn 639 | endfunction 640 | 641 | function! DirDiffNext() 642 | " If the current window is a diff, go down one 643 | call GotoDiffWindow() 644 | " if the current line is <= 6, (within the header range), we go to the 645 | " first diff line open it 646 | if (line(".") < s:DirDiffFirstDiffLine) 647 | exe (s:DirDiffFirstDiffLine) 648 | let b:currentDiff = line(".") 649 | endif 650 | silent! exe (b:currentDiff + 1) 651 | call DirDiffOpen() 652 | endfunction 653 | 654 | function! DirDiffPrev() 655 | " If the current window is a diff, go down one 656 | call GotoDiffWindow() 657 | silent! exe (b:currentDiff - 1) 658 | call DirDiffOpen() 659 | endfunction 660 | 661 | " For each line, we can perform a recursive copy or delete to sync up the 662 | " difference. Returns non-zero if the operation is NOT successful, returns 0 663 | " if everything is fine. 664 | " 665 | function! DirDiffSyncHelper(AB, line) 666 | let fileA = GetFileNameFromLine("A", a:line) 667 | let fileB = GetFileNameFromLine("B", a:line) 668 | " echo "Helper line is ". a:line. " fileA " . fileA . " fileB " . fileB 669 | if IsOnly(a:line) 670 | " If a:AB is "A" and the ParseOnlySrc returns "A", that means we need to 671 | " copy 672 | let fileSrc = ParseOnlySrc(a:line) 673 | let operation = "" 674 | if (a:AB == "A" && fileSrc == "A") 675 | let operation = "Copy" 676 | " Use A, and A has source, thus copy the file from A to B 677 | let fileFrom = fileA 678 | let fileTo = fileB 679 | elseif (a:AB == "A" && fileSrc == "B") 680 | let operation = "Delete" 681 | " Use A, but B has source, thus delete the file from B 682 | let fileFrom = fileB 683 | let fileTo = fileA 684 | elseif (a:AB == "B" && fileSrc == "A") 685 | let operation = "Delete" 686 | " Use B, but the source file is A, thus removing A 687 | let fileFrom = fileA 688 | let fileTo = fileB 689 | elseif (a:AB == "B" && fileSrc == "B") 690 | " Use B, and B has the source file, thus copy B to A 691 | let operation = "Copy" 692 | let fileFrom = fileB 693 | let fileTo = fileA 694 | endif 695 | elseif IsDiffer(a:line) 696 | " Copy no matter what 697 | let operation = "Copy" 698 | if (a:AB == "A") 699 | let fileFrom = fileA 700 | let fileTo = fileB 701 | elseif (a:AB == "B") 702 | let fileFrom = fileB 703 | let fileTo = fileA 704 | endif 705 | else 706 | echo "There is no diff here!" 707 | " Error 708 | return 1 709 | endif 710 | if (operation == "Copy") 711 | let rtnCode = Copy(fileFrom, fileTo) 712 | elseif (operation == "Delete") 713 | let rtnCode = Delete(fileFrom) 714 | endif 715 | return rtnCode 716 | endfunction 717 | 718 | " Synchronize the range 719 | function! DirDiffSync() range 720 | let answer = 1 721 | let silence = 0 722 | let syncMaster = "A" 723 | let currLine = a:firstline 724 | let lastLine = a:lastline 725 | let syncCount = 0 726 | 727 | while ((currLine <= lastLine)) 728 | " Update the highlight 729 | call DeHighlightLine() 730 | let b:currentDiff = currLine 731 | call HighlightLine() 732 | let line = getline(currLine) 733 | if (!silence) 734 | let answer = confirm(substitute(line, "^....", '', ''). "\nSynchronization option:" , "&A -> B\n&B -> A\nA&lways A\nAl&ways B\n&Skip\nCa&ncel", 6) 735 | if (answer == 1 || answer == 3) 736 | let syncMaster = "A" 737 | endif 738 | if (answer == 2 || answer == 4) 739 | let syncMaster = "B" 740 | endif 741 | if (answer == 3 || answer == 4) 742 | let silence = 1 743 | endif 744 | if (answer == 5) 745 | let currLine = currLine + 1 746 | continue 747 | endif 748 | if (answer == 6) 749 | break 750 | endif 751 | endif 752 | 753 | " call DeHighlightLine() 754 | let rtnCode = DirDiffSyncHelper(syncMaster, line) 755 | if (rtnCode == 0) 756 | " Successful 757 | let syncCount = syncCount + 1 758 | " Assume that the line is synchronized, we delete the entry 759 | setlocal modifiable 760 | exe (currLine.",".currLine." delete") 761 | setlocal nomodifiable 762 | setlocal nomodified 763 | let lastLine = lastLine - 1 764 | else 765 | " Failed! 766 | let currLine = currLine + 1 767 | endif 768 | endwhile 769 | redraw! 770 | echo syncCount . " diff item(s) synchronized." 771 | endfunction 772 | 773 | " Return file "A" or "B" depending on the line given. If it's a Only line, 774 | " either A or B does not exist, but the according value would be returned. 775 | function! GetFileNameFromLine(AB, line) 776 | " Determine where the source of the copy is. 777 | let dirA = GetBaseDir("A") 778 | let dirB = GetBaseDir("B") 779 | 780 | let fileToProcess = "" 781 | 782 | if IsOnly(a:line) 783 | let fileToProcess = ParseOnlyFile(a:line) 784 | elseif IsDiffer(a:line) 785 | let regex = '^.*' . s:DirDiffDifferLine . '\[A\]\(.*\)' . s:DirDiffDifferAndLine . '\[B\]\(.*\)' . s:DirDiffDifferEndLine . '.*$' 786 | let fileToProcess = substitute(a:line, regex, '\1', '') 787 | else 788 | endif 789 | 790 | "echo "line : " . a:line. "AB = " . a:AB . " File to Process " . fileToProcess 791 | if (a:AB == "A") 792 | return dirA . fileToProcess 793 | elseif (a:AB == "B") 794 | return dirB . fileToProcess 795 | else 796 | return "" 797 | endif 798 | endfunction 799 | 800 | "Returns the source (A or B) of the "Only" line 801 | function! ParseOnlySrc(line) 802 | return substitute(a:line, '^.*' . s:DirDiffDiffOnlyLine . '\[\(.\)\].*' . s:DirDiffDiffOnlyLineCenter . '.*', '\1', '') 803 | endfunction 804 | 805 | function! ParseOnlyFile(line) 806 | let regex = '^.*' . s:DirDiffDiffOnlyLine . '\[.\]\(.*\)' . s:DirDiffDiffOnlyLineCenter . '\(.*\)' 807 | let root = substitute(a:line, regex , '\1', '') 808 | let file = root . s:sep . substitute(a:line, regex , '\2', '') 809 | return file 810 | endfunction 811 | 812 | function! Copy(fileFromOrig, fileToOrig) 813 | let fileFrom = substitute(a:fileFromOrig, '/', s:sep, 'g') 814 | let fileTo = substitute(a:fileToOrig, '/', s:sep, 'g') 815 | echo "Copy from " . fileFrom . " to " . fileTo 816 | if (s:DirDiffCopyCmd == "") 817 | echo "Copy not supported on this platform" 818 | return 1 819 | endif 820 | 821 | " Constructs the copy command 822 | let copycmd = "!".s:DirDiffCopyCmd." ".s:DirDiffCopyFlags 823 | " Append the interactive flag 824 | if (g:DirDiffInteractive) 825 | let copycmd = copycmd . " " . s:DirDiffCopyInteractiveFlag 826 | endif 827 | let copycmd = copycmd . " \"".fileFrom."\" \"".fileTo."\"" 828 | 829 | " Constructs the copy directory command 830 | let copydircmd = "!".s:DirDiffCopyDirCmd." ".s:DirDiffCopyDirFlags 831 | " Append the interactive flag 832 | if (g:DirDiffInteractive) 833 | let copydircmd = copydircmd . " " . s:DirDiffCopyInteractiveFlag 834 | endif 835 | let copydircmd = copydircmd . " \"".fileFrom."\" \"".fileTo."\"" 836 | 837 | let error = 0 838 | if (isdirectory(fileFrom)) 839 | let error = DirDiffExec(copydircmd, g:DirDiffInteractive) 840 | else 841 | let error = DirDiffExec(copycmd, g:DirDiffInteractive) 842 | endif 843 | if (error != 0) 844 | echo "Can't copy from " . fileFrom . " to " . fileTo 845 | return 1 846 | endif 847 | return 0 848 | endfunction 849 | 850 | " Would execute the command, either silent or not silent, by the 851 | " interactive flag ([0|1]). Returns the v:shell_error after 852 | " executing the command. 853 | function! DirDiffExec(cmd, interactive) 854 | let error = 0 855 | " On Unix, if we use a different shell other than bash, we can cause 856 | " problem 857 | if g:DirDiffForceShell != "" 858 | let shell_save = &shell 859 | let &shell = g:DirDiffForceShell 860 | endif 861 | if (a:interactive) 862 | exe (a:cmd) 863 | let error = v:shell_error 864 | else 865 | silent exe (a:cmd) 866 | let error = v:shell_error 867 | endif 868 | if g:DirDiffForceShell != "" 869 | let &shell = shell_save 870 | endif 871 | " let d = input("DirDiffExec: " . a:cmd . " " . a:interactive . " returns " . v:shell_error) 872 | if !empty(g:DirDiffTheme) 873 | execute "colorscheme ". g:DirDiffTheme 874 | endif 875 | if g:DirDiffSimpleMap 876 | nnoremap [ [c 877 | nnoremap ] ]c 878 | endif 879 | return error 880 | endfunction 881 | 882 | " Delete the file or directory. Returns 0 if nothing goes wrong, error code 883 | " otherwise. 884 | function! Delete(fileFromOrig) 885 | let fileFrom = substitute(a:fileFromOrig, '/', s:sep, 'g') 886 | echo "Deleting from " . fileFrom 887 | if (s:DirDiffDeleteCmd == "") 888 | echo "Delete not supported on this platform" 889 | return 1 890 | endif 891 | 892 | let delcmd = "" 893 | 894 | if (isdirectory(fileFrom)) 895 | let delcmd = "!".s:DirDiffDeleteDirCmd." ".s:DirDiffDeleteDirFlags 896 | if (g:DirDiffInteractive) 897 | " If running on Unix, and we're running in interactive mode, we 898 | " append the -i tag 899 | if (has("unix")) 900 | let delcmd = delcmd . " " . s:DirDiffDeleteInteractiveFlag 901 | endif 902 | else 903 | " If running on windows, and we're not running in interactive 904 | " mode, we append the quite flag to the "rd" command 905 | if (has("win32")) 906 | let delcmd = delcmd . " " . s:DirDiffDeleteDirQuietFlag 907 | endif 908 | endif 909 | else 910 | let delcmd = "!".s:DirDiffDeleteCmd." ".s:DirDiffDeleteFlags 911 | if (g:DirDiffInteractive) 912 | let delcmd = delcmd . " " . s:DirDiffDeleteInteractiveFlag 913 | endif 914 | endif 915 | 916 | let delcmd = delcmd ." \"".fileFrom."\"" 917 | let error = DirDiffExec(delcmd, g:DirDiffInteractive) 918 | if (error != 0) 919 | echo "Can't delete " . fileFrom 920 | endif 921 | return error 922 | endfunction 923 | 924 | " The given line begins with the "Only in" 925 | function! IsOnly(line) 926 | return (match(a:line, "^ *" . s:DirDiffDiffOnlyLine . "\\|^==> " . s:DirDiffDiffOnlyLine ) == 0) 927 | endfunction 928 | 929 | " The given line begins with the "Files" 930 | function! IsDiffer(line) 931 | return (match(a:line, "^ *" . s:DirDiffDifferLine . "\\|^==> " . s:DirDiffDifferLine ) == 0) 932 | endfunction 933 | 934 | " Let you modify the Exclude patthern 935 | function! ChangeExcludes() 936 | let g:DirDiffExcludes = input ("Exclude pattern (separate multiple patterns with ','): ", g:DirDiffExcludes) 937 | echo "\nPress update ('u') to refresh the diff." 938 | endfunction 939 | 940 | " Let you modify additional arguments for diff 941 | function! ChangeArguments() 942 | let g:DirDiffAddArgs = input ("Additional diff args: ", g:DirDiffAddArgs) 943 | echo "\nPress update ('u') to refresh the diff." 944 | endfunction 945 | 946 | " Let you modify the Ignore patthern 947 | function! ChangeIgnore() 948 | let g:DirDiffIgnore = input ("Ignore pattern (separate multiple patterns with ','): ", g:DirDiffIgnore) 949 | echo "\nPress update ('u') to refresh the diff." 950 | endfunction 951 | 952 | " Sorting functions from the Vim docs. Use this instead of the sort binary. 953 | " 954 | " Function for use with Sort(), to compare two strings. 955 | func! Strcmp(str1, str2) 956 | if (a:str1 < a:str2) 957 | return -1 958 | elseif (a:str1 > a:str2) 959 | return 1 960 | else 961 | return 0 962 | endif 963 | endfunction 964 | 965 | " Sort lines. SortR() is called recursively. 966 | func! SortR(start, end, cmp) 967 | if (a:start >= a:end) 968 | return 969 | endif 970 | let partition = a:start - 1 971 | let middle = partition 972 | let partStr = getline((a:start + a:end) / 2) 973 | let i = a:start 974 | while (i <= a:end) 975 | let str = getline(i) 976 | exec "let result = " . a:cmp . "(str, partStr)" 977 | if (result <= 0) 978 | " Need to put it before the partition. Swap lines i and partition. 979 | let partition = partition + 1 980 | if (result == 0) 981 | let middle = partition 982 | endif 983 | if (i != partition) 984 | let str2 = getline(partition) 985 | call setline(i, str2) 986 | call setline(partition, str) 987 | endif 988 | endif 989 | let i = i + 1 990 | endwhile 991 | 992 | " Now we have a pointer to the "middle" element, as far as partitioning 993 | " goes, which could be anywhere before the partition. Make sure it is at 994 | " the end of the partition. 995 | if (middle != partition) 996 | let str = getline(middle) 997 | let str2 = getline(partition) 998 | call setline(middle, str2) 999 | call setline(partition, str) 1000 | endif 1001 | call SortR(a:start, partition - 1, a:cmp) 1002 | call SortR(partition + 1, a:end, a:cmp) 1003 | endfunc 1004 | 1005 | " To Sort a range of lines, pass the range to Sort() along with the name of a 1006 | " function that will compare two lines. 1007 | func! Sort(cmp) range 1008 | call SortR(a:firstline, a:lastline, a:cmp) 1009 | endfunc 1010 | 1011 | " Added to deal with internationalized version of diff, which returns a 1012 | " different string than "Files ... differ" or "Only in ... " 1013 | 1014 | function! GetDiffStrings() 1015 | " Check if we have the dynamic text string turned on. If not, just return 1016 | " what's set in the global variables 1017 | 1018 | if (g:DirDiffDynamicDiffText == 0) 1019 | let s:DirDiffDiffOnlyLineCenter = g:DirDiffTextOnlyInCenter 1020 | let s:DirDiffDiffOnlyLine = g:DirDiffTextOnlyIn 1021 | let s:DirDiffDifferLine = g:DirDiffTextFiles 1022 | let s:DirDiffDifferAndLine = g:DirDiffTextAnd 1023 | let s:DirDiffDifferEndLine = g:DirDiffTextDiffer 1024 | return 1025 | endif 1026 | 1027 | let tmp1 = tempname() 1028 | let tmp2 = tempname() 1029 | let tmpdiff = tempname() 1030 | 1031 | " We need to pad the backslashes in order to make it match 1032 | let tmp1rx = EscapeDirForRegex(tmp1) 1033 | let tmp2rx = EscapeDirForRegex(tmp2) 1034 | let tmpdiffrx = EscapeDirForRegex(tmpdiff) 1035 | 1036 | silent exe s:DirDiffMakeDirCmd . "\"" . tmp1 . "\"" 1037 | silent exe s:DirDiffMakeDirCmd . "\"" . tmp2 . "\"" 1038 | silent exe "!echo test > \"" . tmp1 . s:sep . "test" . "\"" 1039 | silent exe "!" . g:DirDiffLangString . "diff -r --brief \"" . tmp1 . "\" \"" . tmp2 . "\" > \"" . tmpdiff . "\"" 1040 | 1041 | " Now get the result of that diff cmd 1042 | silent exe "split ". tmpdiff 1043 | "echo "First line: " . getline(1) 1044 | "echo "tmp1: " . tmp1 1045 | "echo "tmp1rx: " . tmp1rx 1046 | let regex = '\(^.*\)' . tmp1rx . '\(.*\)' . "test" 1047 | let s:DirDiffDiffOnlyLine = substitute( getline(1), regex, '\1', '') 1048 | let s:DirDiffDiffOnlyLineCenter = substitute( getline(1), regex, '\2', '') 1049 | "echo "DirDiff Only: " . s:DirDiffDiffOnlyLine 1050 | 1051 | bd 1052 | 1053 | " Now let's get the Differ string 1054 | "echo "Getting the diff in GetDiffStrings" 1055 | 1056 | silent exe "!echo testdifferent > \"" . tmp2 . s:sep . "test" . "\"" 1057 | silent exe "!" . g:DirDiffLangString . "diff -r --brief \"" . tmp1 . "\" \"" . tmp2 . "\" > \"" . tmpdiff . "\"" 1058 | 1059 | silent exe "split ". tmpdiff 1060 | let s:DirDiffDifferLine = substitute( getline(1), tmp1rx . ".*$", "", '') 1061 | " Note that the diff on cygwin may output '/' instead of '\' for the 1062 | " separator, so we need to accomodate for both cases 1063 | let andrx = "^.*" . tmp1rx . "[\\\/]test\\(.*\\)" . tmp2rx . "[\\\/]test.*$" 1064 | let endrx = "^.*" . tmp1rx . "[\\\/]test.*" . tmp2rx . "[\\\/]test\\(.*$\\)" 1065 | "echo "andrx : " . andrx 1066 | "echo "endrx : " . endrx 1067 | let s:DirDiffDifferAndLine = substitute( getline(1), andrx , "\\1", '') 1068 | let s:DirDiffDifferEndLine = substitute( getline(1), endrx, "\\1", '') 1069 | 1070 | "echo "s:DirDiffDifferLine = " . s:DirDiffDifferLine 1071 | "echo "s:DirDiffDifferAndLine = " . s:DirDiffDifferAndLine 1072 | "echo "s:DirDiffDifferEndLine = " . s:DirDiffDifferEndLine 1073 | 1074 | q 1075 | 1076 | " Delete tmp files 1077 | "echo "Deleting tmp files." 1078 | 1079 | call Delete(tmp1) 1080 | call Delete(tmp2) 1081 | call Delete(tmpdiff) 1082 | 1083 | "avoid get diff text again 1084 | let g:DirDiffTextOnlyInCenter = s:DirDiffDiffOnlyLineCenter 1085 | let g:DirDiffTextOnlyIn = s:DirDiffDiffOnlyLine 1086 | let g:DirDiffTextFiles = s:DirDiffDifferLine 1087 | let g:DirDiffTextAnd = s:DirDiffDifferAndLine 1088 | let g:DirDiffTextDiffer = s:DirDiffDifferEndLine 1089 | let g:DirDiffDynamicDiffText = 0 1090 | 1091 | endfunction 1092 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/will133/vim-dirdiff/84bc8999fde4b3c2d8b228b560278ab30c7ea4c9/screenshot.png --------------------------------------------------------------------------------