Skip to content

Commit b5a1095

Browse files
committed
feat: Added context menu to delete unversioned files (Close #364)
1 parent 825c3a4 commit b5a1095

File tree

3 files changed

+82
-9
lines changed

3 files changed

+82
-9
lines changed

package.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,11 @@
269269
"command": "svn.treeview.pullIncomingChange",
270270
"title": "Pull selected changes",
271271
"category": "SVN"
272+
},
273+
{
274+
"command": "svn.deleteUnversioned",
275+
"title": "Delete selected files",
276+
"category": "SVN"
272277
}
273278
],
274279
"menus": {
@@ -372,6 +377,10 @@
372377
{
373378
"command": "svn.renameExplorer",
374379
"when": "false"
380+
},
381+
{
382+
"command": "svn.deleteUnversioned",
383+
"when": "false"
375384
}
376385
],
377386
"view/title": [
@@ -518,6 +527,11 @@
518527
"when": "config.svn.enabled && scmProvider == svn && scmResourceGroup != unversioned && scmResourceGroup != external && scmResourceGroup != conflicts && scmResourceGroup != remotechanged",
519528
"group": "2_modification"
520529
},
530+
{
531+
"command": "svn.deleteUnversioned",
532+
"when": "config.svn.enabled && scmProvider == svn && scmResourceGroup == unversioned",
533+
"group": "2_modification"
534+
},
521535
{
522536
"command": "svn.addToIgnoreSCM",
523537
"when": "config.svn.enabled && scmProvider == svn && scmResourceGroup == unversioned",

src/commands.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import { Svn, svnErrorCodes } from "./svn";
4343
import IncomingChangeNode from "./treeView/nodes/incomingChangeNode";
4444
import { fromSvnUri, toSvnUri } from "./uri";
4545
import {
46+
deleteDirectory,
4647
fixPathSeparator,
4748
hasSupportToRegisterDiffCommand,
4849
IDisposable
@@ -1506,6 +1507,44 @@ export class SvnCommands implements IDisposable {
15061507
}
15071508
}
15081509

1510+
@command("svn.deleteUnversioned")
1511+
public async deleteUnversioned(
1512+
...resourceStates: SourceControlResourceState[]
1513+
): Promise<void> {
1514+
const selection = this.getResourceStates(resourceStates);
1515+
1516+
if (selection.length === 0) {
1517+
return;
1518+
}
1519+
1520+
const uris = selection.map(resource => resource.resourceUri);
1521+
1522+
const answer = await window.showWarningMessage(
1523+
"Would you like delete the files?.",
1524+
{ modal: true },
1525+
"Yes",
1526+
"No"
1527+
);
1528+
1529+
if (answer === "Yes") {
1530+
for (const uri of uris) {
1531+
const fsPath = uri.fsPath;
1532+
1533+
if (!fs.existsSync(fsPath)) {
1534+
continue;
1535+
}
1536+
1537+
const stat = fs.lstatSync(fsPath);
1538+
1539+
if (stat.isDirectory()) {
1540+
deleteDirectory(fsPath);
1541+
} else {
1542+
fs.unlinkSync(fsPath);
1543+
}
1544+
}
1545+
}
1546+
}
1547+
15091548
private getSCMResource(uri?: Uri): Resource | undefined {
15101549
uri = uri
15111550
? uri

src/util.ts

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { sep } from "path";
1+
import * as fs from "fs";
2+
import * as path from "path";
23
import { commands, Event, window } from "vscode";
34
import { Operation } from "./common/types";
45

@@ -68,10 +69,10 @@ export function eventToPromise<T>(event: Event<T>): Promise<T> {
6869
return new Promise<T>(c => onceEvent(event)(c));
6970
}
7071

71-
const regexNormalizePath = new RegExp(sep === "/" ? "\\\\" : "/", "g");
72+
const regexNormalizePath = new RegExp(path.sep === "/" ? "\\\\" : "/", "g");
7273
const regexNormalizeWindows = new RegExp("^\\\\(\\w:)", "g");
7374
export function fixPathSeparator(file: string) {
74-
file = file.replace(regexNormalizePath, sep);
75+
file = file.replace(regexNormalizePath, path.sep);
7576
file = file.replace(regexNormalizeWindows, "$1"); // "\t:\test" => "t:\test"
7677
return file;
7778
}
@@ -80,19 +81,19 @@ export function normalizePath(file: string) {
8081
file = fixPathSeparator(file);
8182

8283
// IF Windows
83-
if (sep === "\\") {
84+
if (path.sep === "\\") {
8485
file = file.toLowerCase();
8586
}
8687

8788
return file;
8889
}
8990

9091
export function isDescendant(parent: string, descendant: string): boolean {
91-
parent = parent.replace(/[\\\/]/g, sep);
92-
descendant = descendant.replace(/[\\\/]/g, sep);
92+
parent = parent.replace(/[\\\/]/g, path.sep);
93+
descendant = descendant.replace(/[\\\/]/g, path.sep);
9394

9495
// IF Windows
95-
if (sep === "\\") {
96+
if (path.sep === "\\") {
9697
parent = parent.replace(/^\\/, "").toLowerCase();
9798
descendant = descendant.replace(/^\\/, "").toLowerCase();
9899
}
@@ -101,8 +102,8 @@ export function isDescendant(parent: string, descendant: string): boolean {
101102
return true;
102103
}
103104

104-
if (parent.charAt(parent.length - 1) !== sep) {
105-
parent += sep;
105+
if (parent.charAt(parent.length - 1) !== path.sep) {
106+
parent += path.sep;
106107
}
107108

108109
return descendant.startsWith(parent);
@@ -161,3 +162,22 @@ export function isReadOnly(operation: Operation): boolean {
161162
return false;
162163
}
163164
}
165+
166+
/**
167+
* Remove directory recursively
168+
* @param {string} dirPath
169+
* @see https://stackoverflow.com/a/42505874/3027390
170+
*/
171+
export function deleteDirectory(dirPath: string) {
172+
if (fs.existsSync(dirPath) && fs.lstatSync(dirPath).isDirectory()) {
173+
fs.readdirSync(dirPath).forEach((entry: string) => {
174+
const entryPath = path.join(dirPath, entry);
175+
if (fs.lstatSync(entryPath).isDirectory()) {
176+
deleteDirectory(entryPath);
177+
} else {
178+
fs.unlinkSync(entryPath);
179+
}
180+
});
181+
fs.rmdirSync(dirPath);
182+
}
183+
}

0 commit comments

Comments
 (0)