Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Display non-Java files in Java Projects explorer. [#145](https://github.com/microsoft/vscode-java-dependency/issues/145)
- Apply file decorators to project level. [#481](https://github.com/microsoft/vscode-java-dependency/issues/481)
- Give more hints about the project import status. [#580](https://github.com/microsoft/vscode-java-dependency/issues/580)
- Support creating files and folders in Java Projects explorer. [#598](https://github.com/microsoft/vscode-java-dependency/issues/598)

### Fixed
- Apply `files.exclude` to Java Projects explorer. [#214](https://github.com/microsoft/vscode-java-dependency/issues/214)
Expand Down
37 changes: 34 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,18 @@
"title": "%contributes.commands.java.view.package.newPackage%",
"category": "Java"
},
{
"command": "java.view.package.newFile",
"title": "%contributes.commands.java.view.package.newFile%",
"category": "Java",
"icon": "$(new-file)"
},
{
"command": "java.view.package.newFolder",
"title": "%contributes.commands.java.view.package.newFolder%",
"category": "Java",
"icon": "$(new-folder)"
},
{
"command": "java.view.package.moveFileToTrash",
"title": "%contributes.commands.java.view.package.moveFileToTrash%",
Expand Down Expand Up @@ -361,6 +373,14 @@
"command": "java.view.package.newPackage",
"when": "false"
},
{
"command": "java.view.package.newFile",
"when": "false"
},
{
"command": "java.view.package.newFolder",
"when": "false"
},
{
"command": "java.view.package.renameFile",
"when": "false"
Expand Down Expand Up @@ -530,7 +550,7 @@
},
{
"submenu": "javaProject.new",
"when": "view == javaProjectExplorer && (viewItem =~ /java:(package|packageRoot)(?=.*?\\b\\+source\\b)(?=.*?\\b\\+uri\\b)/ || viewItem =~ /java:project(?=.*?\\b\\+java\\b)(?=.*?\\b\\+uri\\b)/ || viewItem =~ /java:type(?=.*?\\b\\+source\\b)(?=.*?\\b\\+uri\\b)/)",
"when": "view == javaProjectExplorer && viewItem =~ /java(?!:container)(?!:jar)(?!.*?\\b\\+binary\\b)(?=.*?\\b\\+uri\\b)/",
"group": "1_new@10"
},
{
Expand Down Expand Up @@ -590,11 +610,22 @@
"javaProject.new": [
{
"command": "java.view.package.newJavaClass",
"group": "new@10"
"group": "new@10",
"when": "view == javaProjectExplorer && (viewItem =~ /java:(package|packageRoot)(?=.*?\\b\\+source\\b)/ || viewItem =~ /java:project(?=.*?\\b\\+java\\b)/ || viewItem =~ /java:type/)"
},
{
"command": "java.view.package.newPackage",
"group": "new@40"
"group": "new@20",
"when": "view == javaProjectExplorer && (viewItem =~ /java:(package|packageRoot)(?=.*?\\b\\+source\\b)/ || viewItem =~ /java:project(?=.*?\\b\\+java\\b)/ || viewItem =~ /java:type/)"
},
{
"command": "java.view.package.newFile",
"group": "new@30"
},
{
"command": "java.view.package.newFolder",
"group": "new@40",
"when": "view == javaProjectExplorer && (viewItem =~ /java:(file|folder|project)/ || viewItem =~ /java:(packageRoot)(?=.*?\\b\\+resource\\b)/)"
}
]
},
Expand Down
2 changes: 2 additions & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
"contributes.commands.java.view.package.copyRelativeFilePath": "Copy Relative Path",
"contributes.commands.java.view.package.newJavaClass": "Java Class",
"contributes.commands.java.view.package.newPackage": "Package",
"contributes.commands.java.view.package.newFile": "File",
"contributes.commands.java.view.package.newFolder": "Folder",
"contributes.commands.java.view.package.renameFile": "Rename",
"contributes.commands.java.view.package.moveFileToTrash": "Delete",
"contributes.commands.java.view.package.deleteFilePermanently": "Delete Permanently",
Expand Down
2 changes: 2 additions & 0 deletions package.nls.zh-cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
"contributes.commands.java.view.package.copyRelativeFilePath": "复制相对路径",
"contributes.commands.java.view.package.newJavaClass": "Java 类",
"contributes.commands.java.view.package.newPackage": "包",
"contributes.commands.java.view.package.newFile": "文件",
"contributes.commands.java.view.package.newFolder": "文件夹",
"contributes.commands.java.view.package.renameFile": "重命名",
"contributes.commands.java.view.package.moveFileToTrash": "删除",
"contributes.commands.java.view.package.deleteFilePermanently": "永久删除",
Expand Down
2 changes: 2 additions & 0 deletions package.nls.zh-tw.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
"contributes.commands.java.view.package.copyRelativeFilePath": "複製相對路徑",
"contributes.commands.java.view.package.newJavaClass": "Java 類別",
"contributes.commands.java.view.package.newPackage": "套件",
"contributes.commands.java.view.package.newFile": "檔案",
"contributes.commands.java.view.package.newFolder": "資料夾",
"contributes.commands.java.view.package.renameFile": "重新命名",
"contributes.commands.java.view.package.moveFileToTrash": "刪除",
"contributes.commands.java.view.package.deleteFilePermanently": "永久刪除",
Expand Down
4 changes: 4 additions & 0 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ export namespace Commands {

export const VIEW_PACKAGE_REVEAL_IN_PROJECT_EXPLORER = "java.view.package.revealInProjectExplorer";

export const VIEW_PACKAGE_NEW_FILE = "java.view.package.newFile";

export const VIEW_PACKAGE_NEW_FOLDER = "java.view.package.newFolder";

export const VIEW_MENUS_FILE_NEW_JAVA_CLASS = "java.view.menus.file.newJavaClass";

export const JAVA_PROJECT_OPEN = "_java.project.open";
Expand Down
103 changes: 103 additions & 0 deletions src/explorerCommands/new.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { NodeKind } from "../java/nodeData";
import { DataNode } from "../views/dataNode";
import { resourceRoots } from "../views/packageRootNode";
import { checkJavaQualifiedName } from "./utility";
import { sendError, setUserError } from "vscode-extension-telemetry-wrapper";

// TODO: separate to two function to handle creation from menu bar and explorer.
export async function newJavaClass(node?: DataNode): Promise<void> {
Expand Down Expand Up @@ -278,3 +279,105 @@ interface ISourcePath {
projectName: string;
projectType: string;
}

export async function newFile(node: DataNode): Promise<void> {
const basePath = getBasePath(node);
if (!basePath) {
window.showErrorMessage("The selected node is invalid.");
return;
}

const fileName: string | undefined = await window.showInputBox({
placeHolder: "Input the file name",
ignoreFocusOut: true,
validateInput: async (value: string): Promise<string> => {
return validateNewFileFolder(basePath, value);
},
});

if (!fileName) {
return;
}

// any continues separator will be deduplicated.
const relativePath = fileName.replace(/[/\\]+/g, path.sep);
const newFilePath = path.join(basePath, relativePath);
await createFile(newFilePath);
}

async function createFile(newFilePath: string) {
fse.createFile(newFilePath, async (err: Error) => {
if (err) {
setUserError(err);
sendError(err);
const choice = await window.showErrorMessage(
err.message || "Failed to create file: " + path.basename(newFilePath),
"Retry"
);
if (choice === "Retry") {
await createFile(newFilePath);
}
} else {
window.showTextDocument(Uri.file(newFilePath));
}
});
}

export async function newFolder(node: DataNode): Promise<void> {
const basePath = getBasePath(node);
if (!basePath) {
window.showErrorMessage("The selected node is invalid.");
return;
}

const folderName: string | undefined = await window.showInputBox({
placeHolder: "Input the folder name",
ignoreFocusOut: true,
validateInput: async (value: string): Promise<string> => {
return validateNewFileFolder(basePath, value);
},
});

if (!folderName) {
return;
}

// any continues separator will be deduplicated.
const relativePath = folderName.replace(/[/\\]+/g, path.sep);
const newFolderPath = path.join(basePath, relativePath);
fse.mkdirs(newFolderPath);
}

async function validateNewFileFolder(basePath: string, relativePath: string): Promise<string> {
relativePath = relativePath.replace(/[/\\]+/g, path.sep);
if (await fse.pathExists(path.join(basePath, relativePath))) {
return "A file or folder already exists in the target location.";
}

return "";
}

function getBasePath(node: DataNode): string | undefined {
if (!node.uri) {
return undefined;
}

const uri: Uri = Uri.parse(node.uri);
if (uri.scheme !== "file") {
return undefined;
}

const nodeKind = node.nodeData.kind;
switch (nodeKind) {
case NodeKind.Project:
case NodeKind.PackageRoot:
case NodeKind.Package:
case NodeKind.Folder:
return Uri.parse(node.uri!).fsPath;
case NodeKind.PrimaryType:
case NodeKind.File:
return path.dirname(Uri.parse(node.uri).fsPath);
default:
return undefined;
}
}
25 changes: 0 additions & 25 deletions src/views/PrimaryTypeNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { DataNode } from "./dataNode";
import { DocumentSymbolNode } from "./documentSymbolNode";
import { ExplorerNode } from "./explorerNode";
import { ProjectNode } from "./projectNode";
import { IPackageRootNodeData, PackageRootKind } from "../java/packageRootNodeData";

export class PrimaryTypeNode extends DataNode {

Expand Down Expand Up @@ -122,33 +121,9 @@ export class PrimaryTypeNode extends DataNode {
contextValue += "+test";
}

if (this.belongsToSourceRoot() || this.getUnmanagedFolderAncestor()) {
contextValue += "+source";
}

return contextValue;
}

/**
* Check if the type belongs to a source root. Following conditions can cause the
* result to be false:
* - The type belongs to a jar package
* - The type belongs to an unmanaged folder with '.' as its source root.
*/
private belongsToSourceRoot(): boolean {
const rootNodeData = this._rootNode?.nodeData;
if (!rootNodeData) {
return false;
}

const data = <IPackageRootNodeData>rootNodeData;
if (data.entryKind === PackageRootKind.K_SOURCE) {
return true;
}

return false;
}

/**
* @returns ProjectNode if the current node is under an unmanaged folder,
* otherwise undefined.
Expand Down
8 changes: 7 additions & 1 deletion src/views/dependencyExplorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import { instrumentOperationAsVsCodeCommand, sendInfo } from "vscode-extension-telemetry-wrapper";
import { Commands } from "../commands";
import { deleteFiles } from "../explorerCommands/delete";
import { newJavaClass, newPackage } from "../explorerCommands/new";
import { newFile, newFolder, newJavaClass, newPackage } from "../explorerCommands/new";
import { renameFile } from "../explorerCommands/rename";
import { getCmdNode } from "../explorerCommands/utility";
import { Jdtls } from "../java/jdtls";
Expand Down Expand Up @@ -110,6 +110,12 @@ export class DependencyExplorer implements Disposable {
instrumentOperationAsVsCodeCommand(Commands.VIEW_PACKAGE_NEW_JAVA_CLASS, async (node?: DataNode) => {
newJavaClass(node);
}),
instrumentOperationAsVsCodeCommand(Commands.VIEW_PACKAGE_NEW_FILE, async (node: DataNode) => {
newFile(node);
}),
instrumentOperationAsVsCodeCommand(Commands.VIEW_PACKAGE_NEW_FOLDER, async (node: DataNode) => {
newFolder(node);
}),
instrumentOperationAsVsCodeCommand(Commands.VIEW_PACKAGE_NEW_JAVA_PACKAGE, async (node?: DataNode) => {
let cmdNode = getCmdNode(this._dependencyViewer.selection, node);
if (!cmdNode) {
Expand Down
8 changes: 4 additions & 4 deletions test/suite/contextValue.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,19 +89,19 @@ suite("Context Value Tests", () => {
});

test("test class type node", async function() {
assert.ok(/java:type(?=.*?\b\+class\b)(?=.*?\b\+source\b)(?=.*?\b\+uri\b)/.test((await classType.getTreeItem()).contextValue || ""));
assert.ok(/java:type(?=.*?\b\+class\b)(?=.*?\b\+uri\b)/.test((await classType.getTreeItem()).contextValue || ""));
});

test("test test-class type node", async function() {
assert.ok(/java:type(?=.*?\b\+class\b)(?=.*?\b\+test\b)(?=.*?\b\+source\b)(?=.*?\b\+uri\b)/.test((await testClassType.getTreeItem()).contextValue || ""));
assert.ok(/java:type(?=.*?\b\+class\b)(?=.*?\b\+test\b)(?=.*?\b\+uri\b)/.test((await testClassType.getTreeItem()).contextValue || ""));
});

test("test enum type node", async function() {
assert.ok(/java:type(?=.*?\b\+enum\b)(?=.*?\b\+source\b)(?=.*?\b\+uri\b)/.test((await enumType.getTreeItem()).contextValue || ""));
assert.ok(/java:type(?=.*?\b\+enum\b)(?=.*?\b\+uri\b)/.test((await enumType.getTreeItem()).contextValue || ""));
});

test("test interface type node", async function() {
assert.ok(/java:type(?=.*?\b\+interface\b)(?=.*?\b\+source\b)(?=.*?\b\+uri\b)/.test((await interfaceType.getTreeItem()).contextValue || ""));
assert.ok(/java:type(?=.*?\b\+interface\b)(?=.*?\b\+uri\b)/.test((await interfaceType.getTreeItem()).contextValue || ""));
});

test("test folder node", async function() {
Expand Down