Skip to content

Commit ac69033

Browse files
committed
#471 Spaces can be automatically substituted with hyphens or underscores in reference inputs on dialogs.
1 parent 7877900 commit ac69033

File tree

5 files changed

+108
-8
lines changed

5 files changed

+108
-8
lines changed

package.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,21 @@
540540
"default": false,
541541
"description": "Default state of the \"Prune Tags\" checkbox."
542542
},
543+
"git-graph.dialog.general.referenceInputSpaceSubstitution": {
544+
"type": "string",
545+
"enum": [
546+
"None",
547+
"Hyphen",
548+
"Underscore"
549+
],
550+
"enumDescriptions": [
551+
"Don't replace spaces.",
552+
"Replace space characters with hyphens, for example: \"new branch\" -> \"new-branch\".",
553+
"Replace space characters with underscores, for example: \"new branch\" -> \"new_branch\"."
554+
],
555+
"default": "None",
556+
"description": "Specifies a substitution that is automatically performed when space characters are entered or pasted into reference inputs on dialogs (e.g. Create Branch, Add Tag, etc.)."
557+
},
543558
"git-graph.dialog.merge.noCommit": {
544559
"type": "boolean",
545560
"default": false,

src/config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ class Config {
173173
get dialogDefaults(): DialogDefaults {
174174
let resetCommitMode = this.config.get<string>('dialog.resetCurrentBranchToCommit.mode', 'Mixed');
175175
let resetUncommittedMode = this.config.get<string>('dialog.resetUncommittedChanges.mode', 'Mixed');
176+
let refInputSpaceSubstitution = this.config.get<string>('dialog.general.referenceInputSpaceSubstitution', 'None');
176177

177178
return {
178179
addTag: {
@@ -199,6 +200,9 @@ class Config {
199200
prune: !!this.config.get('dialog.fetchRemote.prune', false),
200201
pruneTags: !!this.config.get('dialog.fetchRemote.pruneTags', false)
201202
},
203+
general: {
204+
referenceInputSpaceSubstitution: refInputSpaceSubstitution === 'Hyphen' ? '-' : refInputSpaceSubstitution === 'Underscore' ? '_' : null
205+
},
202206
merge: {
203207
noCommit: !!this.config.get('dialog.merge.noCommit', false),
204208
noFastForward: !!this.config.get('dialog.merge.noFastForward', true),

src/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,9 @@ export interface DialogDefaults {
454454
readonly prune: boolean,
455455
readonly pruneTags: boolean
456456
};
457+
readonly general: {
458+
readonly referenceInputSpaceSubstitution: string | null
459+
};
457460
readonly merge: {
458461
readonly noCommit: boolean,
459462
readonly noFastForward: boolean,

tests/config.test.ts

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,7 @@ describe('Config', () => {
838838
expect(workspaceConfiguration.get).toBeCalledWith('dialog.fetchIntoLocalBranch.forceFetch', false);
839839
expect(workspaceConfiguration.get).toBeCalledWith('dialog.fetchRemote.prune', false);
840840
expect(workspaceConfiguration.get).toBeCalledWith('dialog.fetchRemote.pruneTags', false);
841+
expect(workspaceConfiguration.get).toBeCalledWith('dialog.general.referenceInputSpaceSubstitution', 'None');
841842
expect(workspaceConfiguration.get).toBeCalledWith('dialog.merge.noCommit', false);
842843
expect(workspaceConfiguration.get).toBeCalledWith('dialog.merge.noFastForward', true);
843844
expect(workspaceConfiguration.get).toBeCalledWith('dialog.merge.squashCommits', false);
@@ -874,6 +875,9 @@ describe('Config', () => {
874875
prune: true,
875876
pruneTags: true
876877
},
878+
general: {
879+
referenceInputSpaceSubstitution: null
880+
},
877881
merge: {
878882
noCommit: true,
879883
noFastForward: true,
@@ -939,6 +943,7 @@ describe('Config', () => {
939943
expect(workspaceConfiguration.get).toBeCalledWith('dialog.fetchIntoLocalBranch.forceFetch', false);
940944
expect(workspaceConfiguration.get).toBeCalledWith('dialog.fetchRemote.prune', false);
941945
expect(workspaceConfiguration.get).toBeCalledWith('dialog.fetchRemote.pruneTags', false);
946+
expect(workspaceConfiguration.get).toBeCalledWith('dialog.general.referenceInputSpaceSubstitution', 'None');
942947
expect(workspaceConfiguration.get).toBeCalledWith('dialog.merge.noCommit', false);
943948
expect(workspaceConfiguration.get).toBeCalledWith('dialog.merge.noFastForward', true);
944949
expect(workspaceConfiguration.get).toBeCalledWith('dialog.merge.squashCommits', false);
@@ -975,6 +980,9 @@ describe('Config', () => {
975980
prune: false,
976981
pruneTags: false
977982
},
983+
general: {
984+
referenceInputSpaceSubstitution: null
985+
},
978986
merge: {
979987
noCommit: false,
980988
noFastForward: false,
@@ -1040,6 +1048,7 @@ describe('Config', () => {
10401048
expect(workspaceConfiguration.get).toBeCalledWith('dialog.fetchIntoLocalBranch.forceFetch', false);
10411049
expect(workspaceConfiguration.get).toBeCalledWith('dialog.fetchRemote.prune', false);
10421050
expect(workspaceConfiguration.get).toBeCalledWith('dialog.fetchRemote.pruneTags', false);
1051+
expect(workspaceConfiguration.get).toBeCalledWith('dialog.general.referenceInputSpaceSubstitution', 'None');
10431052
expect(workspaceConfiguration.get).toBeCalledWith('dialog.merge.noCommit', false);
10441053
expect(workspaceConfiguration.get).toBeCalledWith('dialog.merge.noFastForward', true);
10451054
expect(workspaceConfiguration.get).toBeCalledWith('dialog.merge.squashCommits', false);
@@ -1076,6 +1085,9 @@ describe('Config', () => {
10761085
prune: true,
10771086
pruneTags: true
10781087
},
1088+
general: {
1089+
referenceInputSpaceSubstitution: null
1090+
},
10791091
merge: {
10801092
noCommit: true,
10811093
noFastForward: true,
@@ -1141,6 +1153,7 @@ describe('Config', () => {
11411153
expect(workspaceConfiguration.get).toBeCalledWith('dialog.fetchIntoLocalBranch.forceFetch', false);
11421154
expect(workspaceConfiguration.get).toBeCalledWith('dialog.fetchRemote.prune', false);
11431155
expect(workspaceConfiguration.get).toBeCalledWith('dialog.fetchRemote.pruneTags', false);
1156+
expect(workspaceConfiguration.get).toBeCalledWith('dialog.general.referenceInputSpaceSubstitution', 'None');
11441157
expect(workspaceConfiguration.get).toBeCalledWith('dialog.merge.noCommit', false);
11451158
expect(workspaceConfiguration.get).toBeCalledWith('dialog.merge.noFastForward', true);
11461159
expect(workspaceConfiguration.get).toBeCalledWith('dialog.merge.squashCommits', false);
@@ -1177,6 +1190,9 @@ describe('Config', () => {
11771190
prune: false,
11781191
pruneTags: false
11791192
},
1193+
general: {
1194+
referenceInputSpaceSubstitution: null
1195+
},
11801196
merge: {
11811197
noCommit: false,
11821198
noFastForward: false,
@@ -1227,6 +1243,7 @@ describe('Config', () => {
12271243
expect(workspaceConfiguration.get).toBeCalledWith('dialog.fetchIntoLocalBranch.forceFetch', false);
12281244
expect(workspaceConfiguration.get).toBeCalledWith('dialog.fetchRemote.prune', false);
12291245
expect(workspaceConfiguration.get).toBeCalledWith('dialog.fetchRemote.pruneTags', false);
1246+
expect(workspaceConfiguration.get).toBeCalledWith('dialog.general.referenceInputSpaceSubstitution', 'None');
12301247
expect(workspaceConfiguration.get).toBeCalledWith('dialog.merge.noCommit', false);
12311248
expect(workspaceConfiguration.get).toBeCalledWith('dialog.merge.noFastForward', true);
12321249
expect(workspaceConfiguration.get).toBeCalledWith('dialog.merge.squashCommits', false);
@@ -1263,6 +1280,9 @@ describe('Config', () => {
12631280
prune: false,
12641281
pruneTags: false
12651282
},
1283+
general: {
1284+
referenceInputSpaceSubstitution: null
1285+
},
12661286
merge: {
12671287
noCommit: false,
12681288
noFastForward: true,
@@ -1306,6 +1326,7 @@ describe('Config', () => {
13061326
expect(workspaceConfiguration.get).toBeCalledWith('dialog.fetchIntoLocalBranch.forceFetch', false);
13071327
expect(workspaceConfiguration.get).toBeCalledWith('dialog.fetchRemote.prune', false);
13081328
expect(workspaceConfiguration.get).toBeCalledWith('dialog.fetchRemote.pruneTags', false);
1329+
expect(workspaceConfiguration.get).toBeCalledWith('dialog.general.referenceInputSpaceSubstitution', 'None');
13091330
expect(workspaceConfiguration.get).toBeCalledWith('dialog.merge.noCommit', false);
13101331
expect(workspaceConfiguration.get).toBeCalledWith('dialog.merge.noFastForward', true);
13111332
expect(workspaceConfiguration.get).toBeCalledWith('dialog.merge.squashCommits', false);
@@ -1342,6 +1363,9 @@ describe('Config', () => {
13421363
prune: false,
13431364
pruneTags: false
13441365
},
1366+
general: {
1367+
referenceInputSpaceSubstitution: null
1368+
},
13451369
merge: {
13461370
noCommit: false,
13471371
noFastForward: true,
@@ -1371,7 +1395,7 @@ describe('Config', () => {
13711395
});
13721396

13731397
describe('dialogDefaults.addTag.type', () => {
1374-
it('Should return "annotated" the configuration value is "Annotated"', () => {
1398+
it('Should return TagType.Annotated when the configuration value is "Annotated"', () => {
13751399
// Setup
13761400
vscode.mockExtensionSettingReturnValue('dialog.addTag.type', 'Annotated');
13771401

@@ -1382,7 +1406,7 @@ describe('Config', () => {
13821406
expect(value.addTag.type).toBe(TagType.Annotated);
13831407
});
13841408

1385-
it('Should return "lightweight" the configuration value is "Annotated"', () => {
1409+
it('Should return TagType.Lightweight when the configuration value is "Lightweight"', () => {
13861410
// Setup
13871411
vscode.mockExtensionSettingReturnValue('dialog.addTag.type', 'Lightweight');
13881412

@@ -1394,8 +1418,54 @@ describe('Config', () => {
13941418
});
13951419
});
13961420

1421+
describe('dialogDefaults.general.referenceInputSpaceSubstitution', () => {
1422+
it('Should return NULL when the configuration value is "None"', () => {
1423+
// Setup
1424+
vscode.mockExtensionSettingReturnValue('dialog.general.referenceInputSpaceSubstitution', 'None');
1425+
1426+
// Run
1427+
const value = config.dialogDefaults;
1428+
1429+
// Assert
1430+
expect(value.general.referenceInputSpaceSubstitution).toBe(null);
1431+
});
1432+
1433+
it('Should return "-" when the configuration value is "Hyphen"', () => {
1434+
// Setup
1435+
vscode.mockExtensionSettingReturnValue('dialog.general.referenceInputSpaceSubstitution', 'Hyphen');
1436+
1437+
// Run
1438+
const value = config.dialogDefaults;
1439+
1440+
// Assert
1441+
expect(value.general.referenceInputSpaceSubstitution).toBe('-');
1442+
});
1443+
1444+
it('Should return "_" when the configuration value is "Underscore"', () => {
1445+
// Setup
1446+
vscode.mockExtensionSettingReturnValue('dialog.general.referenceInputSpaceSubstitution', 'Underscore');
1447+
1448+
// Run
1449+
const value = config.dialogDefaults;
1450+
1451+
// Assert
1452+
expect(value.general.referenceInputSpaceSubstitution).toBe('_');
1453+
});
1454+
1455+
it('Should return the default value (NULL) when the configuration value is invalid', () => {
1456+
// Setup
1457+
vscode.mockExtensionSettingReturnValue('dialog.general.referenceInputSpaceSubstitution', 'invalid');
1458+
1459+
// Run
1460+
const value = config.dialogDefaults;
1461+
1462+
// Assert
1463+
expect(value.general.referenceInputSpaceSubstitution).toBe(null);
1464+
});
1465+
});
1466+
13971467
describe('dialogDefaults.resetCommit.mode', () => {
1398-
it('Should return GitResetMode.Hard the configuration value is "Hard"', () => {
1468+
it('Should return GitResetMode.Hard when the configuration value is "Hard"', () => {
13991469
// Setup
14001470
vscode.mockExtensionSettingReturnValue('dialog.resetCurrentBranchToCommit.mode', 'Hard');
14011471

@@ -1406,7 +1476,7 @@ describe('Config', () => {
14061476
expect(value.resetCommit.mode).toBe(GitResetMode.Hard);
14071477
});
14081478

1409-
it('Should return GitResetMode.Mixed the configuration value is "Mixed"', () => {
1479+
it('Should return GitResetMode.Mixed when the configuration value is "Mixed"', () => {
14101480
// Setup
14111481
vscode.mockExtensionSettingReturnValue('dialog.resetCurrentBranchToCommit.mode', 'Mixed');
14121482

@@ -1417,7 +1487,7 @@ describe('Config', () => {
14171487
expect(value.resetCommit.mode).toBe(GitResetMode.Mixed);
14181488
});
14191489

1420-
it('Should return GitResetMode.Soft the configuration value is "Soft"', () => {
1490+
it('Should return GitResetMode.Soft when the configuration value is "Soft"', () => {
14211491
// Setup
14221492
vscode.mockExtensionSettingReturnValue('dialog.resetCurrentBranchToCommit.mode', 'Soft');
14231493

@@ -1430,7 +1500,7 @@ describe('Config', () => {
14301500
});
14311501

14321502
describe('dialogDefaults.resetUncommitted.mode', () => {
1433-
it('Should return GitResetMode.Hard the configuration value is "Hard"', () => {
1503+
it('Should return GitResetMode.Hard when the configuration value is "Hard"', () => {
14341504
// Setup
14351505
vscode.mockExtensionSettingReturnValue('dialog.resetUncommittedChanges.mode', 'Hard');
14361506

@@ -1441,7 +1511,7 @@ describe('Config', () => {
14411511
expect(value.resetUncommitted.mode).toBe(GitResetMode.Hard);
14421512
});
14431513

1444-
it('Should return GitResetMode.Mixed the configuration value is "Mixed"', () => {
1514+
it('Should return GitResetMode.Mixed when the configuration value is "Mixed"', () => {
14451515
// Setup
14461516
vscode.mockExtensionSettingReturnValue('dialog.resetUncommittedChanges.mode', 'Mixed');
14471517

web/dialog.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ class Dialog {
9191
private type: DialogType | null = null;
9292
private customSelects: { [inputIndex: string]: CustomSelect } = {};
9393

94+
private static readonly WHITESPACE_REGEXP = /\s/gu;
95+
9496
/**
9597
* Show a confirmation dialog to the user.
9698
* @param message A message outlining what the user is being asked to confirm.
@@ -269,7 +271,13 @@ class Dialog {
269271
if (dialogInput.value === '') this.elem!.classList.add(CLASS_DIALOG_NO_INPUT);
270272
dialogInput.addEventListener('keyup', () => {
271273
if (this.elem === null) return;
272-
let noInput = dialogInput.value === '', invalidInput = dialogInput.value.match(REF_INVALID_REGEX) !== null;
274+
if (initialState.config.dialogDefaults.general.referenceInputSpaceSubstitution !== null) {
275+
const selectionStart = dialogInput.selectionStart, selectionEnd = dialogInput.selectionEnd;
276+
dialogInput.value = dialogInput.value.replace(Dialog.WHITESPACE_REGEXP, initialState.config.dialogDefaults.general.referenceInputSpaceSubstitution);
277+
dialogInput.selectionStart = selectionStart;
278+
dialogInput.selectionEnd = selectionEnd;
279+
}
280+
const noInput = dialogInput.value === '', invalidInput = dialogInput.value.match(REF_INVALID_REGEX) !== null;
273281
alterClass(this.elem, CLASS_DIALOG_NO_INPUT, noInput);
274282
if (alterClass(this.elem, CLASS_DIALOG_INPUT_INVALID, !noInput && invalidInput)) {
275283
dialogAction.title = invalidInput ? 'Unable to ' + actionName + ', one or more invalid characters entered.' : '';

0 commit comments

Comments
 (0)