Skip to content

Commit cc97d37

Browse files
committed
✨ multi-select (un)stage & discard git commands
1 parent 0c863e6 commit cc97d37

3 files changed

Lines changed: 47 additions & 15 deletions

File tree

extensions/git/src/commands.ts

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { Model, Resource, Status, CommitOptions } from './model';
1111
import * as staging from './staging';
1212
import * as path from 'path';
1313
import * as os from 'os';
14+
import { uniqueFilter } from './util';
1415
import TelemetryReporter from 'vscode-extension-telemetry';
1516
import * as nls from 'vscode-nls';
1617

@@ -272,14 +273,14 @@ export class CommandCenter {
272273
}
273274

274275
@command('git.stage')
275-
async stage(uri?: Uri): Promise<void> {
276-
const resource = this.resolveSCMResource(uri);
276+
async stage(...uris: Uri[]): Promise<void> {
277+
const resources = this.toSCMResources(uris);
277278

278-
if (!resource) {
279+
if (!resources.length) {
279280
return;
280281
}
281282

282-
return await this.model.add(resource);
283+
return await this.model.add(...resources);
283284
}
284285

285286
@command('git.stageAll')
@@ -369,14 +370,14 @@ export class CommandCenter {
369370
}
370371

371372
@command('git.unstage')
372-
async unstage(uri?: Uri): Promise<void> {
373-
const resource = this.resolveSCMResource(uri);
373+
async unstage(...uris: Uri[]): Promise<void> {
374+
const resources = this.toSCMResources(uris);
374375

375-
if (!resource) {
376+
if (!resources.length) {
376377
return;
377378
}
378379

379-
return await this.model.revertFiles(resource);
380+
return await this.model.revertFiles(...resources);
380381
}
381382

382383
@command('git.unstageAll')
@@ -427,23 +428,25 @@ export class CommandCenter {
427428
}
428429

429430
@command('git.clean')
430-
async clean(uri?: Uri): Promise<void> {
431-
const resource = this.resolveSCMResource(uri);
431+
async clean(...uris: Uri[]): Promise<void> {
432+
const resources = this.toSCMResources(uris);
432433

433-
if (!resource) {
434+
if (!resources.length) {
434435
return;
435436
}
436437

437-
const basename = path.basename(resource.uri.fsPath);
438-
const message = localize('confirm discard', "Are you sure you want to discard changes in {0}?", basename);
438+
const message = resources.length === 1
439+
? localize('confirm discard', "Are you sure you want to discard changes in {0}?", path.basename(resources[0].uri.fsPath))
440+
: localize('confirm discard multiple', "Are you sure you want to discard changes in {0} files?", resources.length);
441+
439442
const yes = localize('discard', "Discard Changes");
440443
const pick = await window.showWarningMessage(message, { modal: true }, yes);
441444

442445
if (pick !== yes) {
443446
return;
444447
}
445448

446-
await this.model.clean(resource);
449+
await this.model.clean(...resources);
447450
}
448451

449452
@command('git.cleanAll')
@@ -773,7 +776,7 @@ export class CommandCenter {
773776
uri = uri || window.activeTextEditor && window.activeTextEditor.document.uri;
774777

775778
if (!uri) {
776-
return;
779+
return undefined;
777780
}
778781

779782
if (uri.scheme === 'scm' && uri.authority === 'git') {
@@ -793,6 +796,12 @@ export class CommandCenter {
793796
}
794797
}
795798

799+
private toSCMResources(uris: Uri[]): Resource[] {
800+
return uris.filter(uniqueFilter(uri => uri.toString()))
801+
.map(uri => this.resolveSCMResource(uri))
802+
.filter(r => !!r) as Resource[];
803+
}
804+
796805
dispose(): void {
797806
this.disposables.forEach(d => d.dispose());
798807
}

extensions/git/src/util.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,4 +147,19 @@ export async function mkdirp(path: string, mode?: number): Promise<boolean> {
147147
}
148148

149149
return true;
150+
}
151+
152+
export function uniqueFilter<T>(keyFn: (t: T) => string): (t: T) => boolean {
153+
const seen: { [key: string]: boolean; } = Object.create(null);
154+
155+
return element => {
156+
const key = keyFn(element);
157+
158+
if (seen[key]) {
159+
return false;
160+
}
161+
162+
seen[key] = true;
163+
return true;
164+
};
150165
}

src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,14 @@ class MultipleSelectionActionRunner extends ActionRunner {
105105
super();
106106
}
107107

108+
/**
109+
* Calls the action.run method with the current selection. Note
110+
* that these actions already have the current scm resource context
111+
* within, so we don't want to pass in the selection if there is only
112+
* one selected element. The user should be able to select a single
113+
* item and run an action on another element and only the latter should
114+
* be influenced.
115+
*/
108116
runAction(action: IAction, context?: any): TPromise<any> {
109117
if (action instanceof MenuItemAction) {
110118
const selection = this.getSelectedResources();

0 commit comments

Comments
 (0)