@@ -15,7 +15,7 @@ import { toErrorMessage } from 'vs/base/common/errorMessage';
15
15
import * as strings from 'vs/base/common/strings' ;
16
16
import { Action } from 'vs/base/common/actions' ;
17
17
import { dispose , IDisposable } from 'vs/base/common/lifecycle' ;
18
- import { VIEWLET_ID , IExplorerService } from 'vs/workbench/contrib/files/common/files' ;
18
+ import { VIEWLET_ID , IExplorerService , IFilesConfiguration } from 'vs/workbench/contrib/files/common/files' ;
19
19
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles' ;
20
20
import { IFileService , AutoSaveConfiguration } from 'vs/platform/files/common/files' ;
21
21
import { toResource , SideBySideEditor } from 'vs/workbench/common/editor' ;
@@ -328,7 +328,7 @@ function containsBothDirectoryAndFile(distinctElements: ExplorerItem[]): boolean
328
328
}
329
329
330
330
331
- export function findValidPasteFileTarget ( targetFolder : ExplorerItem , fileToPaste : { resource : URI , isDirectory ?: boolean , allowOverwrite : boolean } ) : URI {
331
+ export function findValidPasteFileTarget ( targetFolder : ExplorerItem , fileToPaste : { resource : URI , isDirectory ?: boolean , allowOverwrite : boolean } , incrementalNaming : 'simple' | 'smart' ) : URI {
332
332
let name = resources . basenameOrAuthority ( fileToPaste . resource ) ;
333
333
334
334
let candidate = resources . joinPath ( targetFolder . resource , name ) ;
@@ -337,37 +337,104 @@ export function findValidPasteFileTarget(targetFolder: ExplorerItem, fileToPaste
337
337
break ;
338
338
}
339
339
340
- name = incrementFileName ( name , ! ! fileToPaste . isDirectory ) ;
340
+ name = incrementFileName ( name , ! ! fileToPaste . isDirectory , incrementalNaming ) ;
341
341
candidate = resources . joinPath ( targetFolder . resource , name ) ;
342
342
}
343
343
344
344
return candidate ;
345
345
}
346
346
347
- export function incrementFileName ( name : string , isFolder : boolean ) : string {
348
- let namePrefix = name ;
349
- let extSuffix = '' ;
350
- if ( ! isFolder ) {
351
- extSuffix = extname ( name ) ;
352
- namePrefix = basename ( name , extSuffix ) ;
353
- }
354
-
355
- // name copy 5(.txt) => name copy 6(.txt)
356
- // name copy(.txt) => name copy 2(.txt)
357
- const suffixRegex = / ^ ( .+ c o p y ) ( \d + ) ? $ / ;
358
- if ( suffixRegex . test ( namePrefix ) ) {
359
- return namePrefix . replace ( suffixRegex , ( match , g1 ?, g2 ?) => {
360
- let number = ( g2 ? parseInt ( g2 ) : 1 ) ;
361
- return number === 0
362
- ? `${ g1 } `
363
- : ( number < Constants . MAX_SAFE_SMALL_INTEGER
364
- ? `${ g1 } ${ number + 1 } `
365
- : `${ g1 } ${ g2 } copy` ) ;
366
- } ) + extSuffix ;
367
- }
368
-
369
- // name(.txt) => name copy(.txt)
370
- return `${ namePrefix } copy${ extSuffix } ` ;
347
+ export function incrementFileName ( name : string , isFolder : boolean , incrementalNaming : 'simple' | 'smart' ) : string {
348
+ if ( incrementalNaming === 'simple' ) {
349
+ let namePrefix = name ;
350
+ let extSuffix = '' ;
351
+ if ( ! isFolder ) {
352
+ extSuffix = extname ( name ) ;
353
+ namePrefix = basename ( name , extSuffix ) ;
354
+ }
355
+
356
+ // name copy 5(.txt) => name copy 6(.txt)
357
+ // name copy(.txt) => name copy 2(.txt)
358
+ const suffixRegex = / ^ ( .+ c o p y ) ( \d + ) ? $ / ;
359
+ if ( suffixRegex . test ( namePrefix ) ) {
360
+ return namePrefix . replace ( suffixRegex , ( match , g1 ?, g2 ?) => {
361
+ let number = ( g2 ? parseInt ( g2 ) : 1 ) ;
362
+ return number === 0
363
+ ? `${ g1 } `
364
+ : ( number < Constants . MAX_SAFE_SMALL_INTEGER
365
+ ? `${ g1 } ${ number + 1 } `
366
+ : `${ g1 } ${ g2 } copy` ) ;
367
+ } ) + extSuffix ;
368
+ }
369
+
370
+ // name(.txt) => name copy(.txt)
371
+ return `${ namePrefix } copy${ extSuffix } ` ;
372
+ }
373
+
374
+ const separators = '[\\.\\-_]' ;
375
+ const maxNumber = Constants . MAX_SAFE_SMALL_INTEGER ;
376
+
377
+ // file.1.txt=>file.2.txt
378
+ let suffixFileRegex = RegExp ( '(.*' + separators + ')(\\d+)(\\..*)$' ) ;
379
+ if ( ! isFolder && name . match ( suffixFileRegex ) ) {
380
+ return name . replace ( suffixFileRegex , ( match , g1 ?, g2 ?, g3 ?) => {
381
+ let number = parseInt ( g2 ) ;
382
+ return number < maxNumber
383
+ ? g1 + strings . pad ( number + 1 , g2 . length ) + g3
384
+ : strings . format ( '{0}{1}.1{2}' , g1 , g2 , g3 ) ;
385
+ } ) ;
386
+ }
387
+
388
+ // 1.file.txt=>2.file.txt
389
+ let prefixFileRegex = RegExp ( '(\\d+)(' + separators + '.*)(\\..*)$' ) ;
390
+ if ( ! isFolder && name . match ( prefixFileRegex ) ) {
391
+ return name . replace ( prefixFileRegex , ( match , g1 ?, g2 ?, g3 ?) => {
392
+ let number = parseInt ( g1 ) ;
393
+ return number < maxNumber
394
+ ? strings . pad ( number + 1 , g1 . length ) + g2 + g3
395
+ : strings . format ( '{0}{1}.1{2}' , g1 , g2 , g3 ) ;
396
+ } ) ;
397
+ }
398
+
399
+ // 1.txt=>2.txt
400
+ let prefixFileNoNameRegex = RegExp ( '(\\d+)(\\..*)$' ) ;
401
+ if ( ! isFolder && name . match ( prefixFileNoNameRegex ) ) {
402
+ return name . replace ( prefixFileNoNameRegex , ( match , g1 ?, g2 ?) => {
403
+ let number = parseInt ( g1 ) ;
404
+ return number < maxNumber
405
+ ? strings . pad ( number + 1 , g1 . length ) + g2
406
+ : strings . format ( '{0}.1{1}' , g1 , g2 ) ;
407
+ } ) ;
408
+ }
409
+
410
+ // file.txt=>file.1.txt
411
+ const lastIndexOfDot = name . lastIndexOf ( '.' ) ;
412
+ if ( ! isFolder && lastIndexOfDot >= 0 ) {
413
+ return strings . format ( '{0}.1{1}' , name . substr ( 0 , lastIndexOfDot ) , name . substr ( lastIndexOfDot ) ) ;
414
+ }
415
+
416
+ // folder.1=>folder.2
417
+ if ( isFolder && name . match ( / ( \d + ) $ / ) ) {
418
+ return name . replace ( / ( \d + ) $ / , ( match : string , ...groups : any [ ] ) => {
419
+ let number = parseInt ( groups [ 0 ] ) ;
420
+ return number < maxNumber
421
+ ? strings . pad ( number + 1 , groups [ 0 ] . length )
422
+ : strings . format ( '{0}.1' , groups [ 0 ] ) ;
423
+ } ) ;
424
+ }
425
+
426
+ // 1.folder=>2.folder
427
+ if ( isFolder && name . match ( / ^ ( \d + ) / ) ) {
428
+ return name . replace ( / ^ ( \d + ) ( .* ) $ / , ( match : string , ...groups : any [ ] ) => {
429
+ let number = parseInt ( groups [ 0 ] ) ;
430
+ return number < maxNumber
431
+ ? strings . pad ( number + 1 , groups [ 0 ] . length ) + groups [ 1 ]
432
+ : strings . format ( '{0}{1}.1' , groups [ 0 ] , groups [ 1 ] ) ;
433
+ } ) ;
434
+ }
435
+
436
+ // file/folder=>file.1/folder.1
437
+ return strings . format ( '{0}.1' , name ) ;
371
438
}
372
439
373
440
// Global Compare with
@@ -1006,6 +1073,7 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => {
1006
1073
const textFileService = accessor . get ( ITextFileService ) ;
1007
1074
const notificationService = accessor . get ( INotificationService ) ;
1008
1075
const editorService = accessor . get ( IEditorService ) ;
1076
+ const configurationService = accessor . get ( IConfigurationService ) ;
1009
1077
1010
1078
if ( listService . lastFocusedList ) {
1011
1079
const explorerContext = getContext ( listService . lastFocusedList ) ;
@@ -1030,7 +1098,8 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => {
1030
1098
target = element . isDirectory ? element : element . parent ! ;
1031
1099
}
1032
1100
1033
- const targetFile = findValidPasteFileTarget ( target , { resource : fileToPaste , isDirectory : fileToPasteStat . isDirectory , allowOverwrite : pasteShouldMove } ) ;
1101
+ const incrementalNaming = configurationService . getValue < IFilesConfiguration > ( ) . explorer . incrementalNaming ;
1102
+ const targetFile = findValidPasteFileTarget ( target , { resource : fileToPaste , isDirectory : fileToPasteStat . isDirectory , allowOverwrite : pasteShouldMove } , incrementalNaming ) ;
1034
1103
1035
1104
// Move/Copy File
1036
1105
if ( pasteShouldMove ) {
0 commit comments