feat: Support 'go to test' command (#1332)
This commit is contained in:
Родитель
40e6dee508
Коммит
f7a04fc692
|
@ -12,6 +12,7 @@
|
|||
<command id="vscode.java.test.findTestTypesAndMethods" />
|
||||
<command id="vscode.java.test.resolvePath" />
|
||||
<command id="vscode.java.test.findTestLocation" />
|
||||
<command id="vscode.java.test.navigateToTestOrTarget" />
|
||||
</delegateCommandHandler>
|
||||
</extension>
|
||||
</plugin>
|
||||
|
|
|
@ -14,6 +14,7 @@ package com.microsoft.java.test.plugin.handler;
|
|||
import com.microsoft.java.test.plugin.launchers.JUnitLaunchUtils;
|
||||
import com.microsoft.java.test.plugin.util.ProjectTestUtils;
|
||||
import com.microsoft.java.test.plugin.util.TestGenerationUtils;
|
||||
import com.microsoft.java.test.plugin.util.TestNavigationUtils;
|
||||
import com.microsoft.java.test.plugin.util.TestSearchUtils;
|
||||
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
|
@ -34,6 +35,7 @@ public class TestDelegateCommandHandler implements IDelegateCommandHandler {
|
|||
private static final String FIND_TYPES_AND_METHODS = "vscode.java.test.findTestTypesAndMethods";
|
||||
private static final String RESOLVE_PATH = "vscode.java.test.resolvePath";
|
||||
private static final String FIND_TEST_LOCATION = "vscode.java.test.findTestLocation";
|
||||
private static final String NAVIGATE_TO_TEST_OR_TARGET = "vscode.java.test.navigateToTestOrTarget";
|
||||
|
||||
@Override
|
||||
public Object executeCommand(String commandId, List<Object> arguments, IProgressMonitor monitor) throws Exception {
|
||||
|
@ -60,6 +62,8 @@ public class TestDelegateCommandHandler implements IDelegateCommandHandler {
|
|||
return TestSearchUtils.resolvePath(arguments, monitor);
|
||||
case FIND_TEST_LOCATION:
|
||||
return TestSearchUtils.findTestLocation(arguments, monitor);
|
||||
case NAVIGATE_TO_TEST_OR_TARGET:
|
||||
return TestNavigationUtils.findTestOrTarget(arguments, monitor);
|
||||
default:
|
||||
throw new UnsupportedOperationException(
|
||||
String.format("Java test plugin doesn't support the command '%s'.", commandId));
|
||||
|
|
|
@ -0,0 +1,224 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 Microsoft Corporation and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Microsoft Corporation - initial API and implementation
|
||||
*******************************************************************************/
|
||||
|
||||
package com.microsoft.java.test.plugin.util;
|
||||
|
||||
import org.eclipse.core.resources.IFile;
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.core.runtime.Path;
|
||||
import org.eclipse.jdt.core.IClasspathEntry;
|
||||
import org.eclipse.jdt.core.ICompilationUnit;
|
||||
import org.eclipse.jdt.core.IJavaElement;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
import org.eclipse.jdt.core.IType;
|
||||
import org.eclipse.jdt.core.JavaModelException;
|
||||
import org.eclipse.jdt.core.search.IJavaSearchConstants;
|
||||
import org.eclipse.jdt.core.search.IJavaSearchScope;
|
||||
import org.eclipse.jdt.core.search.SearchEngine;
|
||||
import org.eclipse.jdt.core.search.SearchPattern;
|
||||
import org.eclipse.jdt.core.search.TypeNameRequestor;
|
||||
import org.eclipse.jdt.ls.core.internal.JDTUtils;
|
||||
import org.eclipse.jdt.ls.core.internal.JDTUtils.LocationType;
|
||||
import org.eclipse.jdt.ls.core.internal.ProjectUtils;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Utils for test navigation features
|
||||
*/
|
||||
public class TestNavigationUtils {
|
||||
/**
|
||||
* find test or test target according to the given java source file uri.
|
||||
* @param arguments arguments
|
||||
* @param monitor monitor
|
||||
* @return the search result for test navigation
|
||||
* @throws JavaModelException
|
||||
*/
|
||||
public static TestNavigationResult findTestOrTarget(List<Object> arguments, IProgressMonitor monitor)
|
||||
throws JavaModelException {
|
||||
if (arguments == null || arguments.size() < 2) {
|
||||
throw new IllegalArgumentException("Wrong arguments passed to findTestOrTarget().");
|
||||
}
|
||||
final String typeUri = (String) arguments.get(0);
|
||||
final ICompilationUnit unit = JDTUtils.resolveCompilationUnit(typeUri);
|
||||
if (unit == null) {
|
||||
JUnitPlugin.logError("Failed to resolve compilation unit from " + typeUri);
|
||||
return null;
|
||||
}
|
||||
final boolean goToTest = (boolean) arguments.get(1);
|
||||
final IType primaryType = unit.findPrimaryType();
|
||||
final String typeName;
|
||||
Location location = null;
|
||||
if (primaryType != null) {
|
||||
typeName = primaryType.getElementName();
|
||||
location = JDTUtils.toLocation(primaryType, LocationType.NAME_RANGE);
|
||||
} else {
|
||||
typeName = unit.getElementName().substring(0, unit.getElementName().lastIndexOf(".java"));
|
||||
}
|
||||
final SearchEngine searchEngine = new SearchEngine();
|
||||
final IJavaProject javaProject = unit.getJavaProject();
|
||||
final IJavaSearchScope scope = goToTest ? getSearchScopeForTest() :
|
||||
getSearchScopeForTarget();
|
||||
final Set<TestNavigationItem> items = new HashSet<>();
|
||||
searchEngine.searchAllTypeNames(
|
||||
null,
|
||||
SearchPattern.R_EXACT_MATCH,
|
||||
("*" + typeName + "*").toCharArray(),
|
||||
SearchPattern.R_PREFIX_MATCH,
|
||||
IJavaSearchConstants.CLASS,
|
||||
scope,
|
||||
new TestNavigationNameRequestor(items, javaProject, typeName),
|
||||
IJavaSearchConstants.WAIT_UNTIL_READY_TO_SEARCH,
|
||||
monitor
|
||||
);
|
||||
|
||||
return new TestNavigationResult(items, location);
|
||||
}
|
||||
|
||||
private static IJavaSearchScope getSearchScopeForTarget() {
|
||||
// TODO: unimplemented
|
||||
return null;
|
||||
}
|
||||
|
||||
private static IJavaSearchScope getSearchScopeForTest() throws JavaModelException {
|
||||
final List<IJavaElement> javaElements = new LinkedList<>();
|
||||
final IJavaProject[] javaProjects = ProjectUtils.getJavaProjects();
|
||||
for (final IJavaProject project : javaProjects) {
|
||||
final List<IClasspathEntry> testEntries = ProjectTestUtils.getTestEntries(project);
|
||||
for (final IClasspathEntry entry : testEntries) {
|
||||
javaElements.addAll(Arrays.asList(project.findPackageFragmentRoots(entry)));
|
||||
}
|
||||
}
|
||||
|
||||
return SearchEngine.createJavaSearchScope(javaElements.toArray(new IJavaElement[0]));
|
||||
}
|
||||
|
||||
static final class TestNavigationNameRequestor extends TypeNameRequestor {
|
||||
private final Set<TestNavigationItem> results;
|
||||
private final IJavaProject javaProject;
|
||||
private final String typeName;
|
||||
|
||||
private TestNavigationNameRequestor(Set<TestNavigationItem> results, IJavaProject javaProject,
|
||||
String typeName) {
|
||||
this.results = results;
|
||||
this.javaProject = javaProject;
|
||||
this.typeName = typeName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void acceptType(int modifiers, char[] packageName, char[] simpleTypeName,
|
||||
char[][] enclosingTypeNames, String path) {
|
||||
if (!path.endsWith(".java")) {
|
||||
return;
|
||||
}
|
||||
|
||||
final IPath fullPath = new Path(path);
|
||||
final IFile file = javaProject.getProject().getFile(
|
||||
fullPath.makeRelativeTo(javaProject.getProject().getFullPath()));
|
||||
if (!file.exists()) {
|
||||
return;
|
||||
}
|
||||
final String uri = file.getLocation().toFile().toURI().toString();
|
||||
final String simpleName;
|
||||
if (enclosingTypeNames.length == 0) {
|
||||
simpleName = String.valueOf(simpleTypeName);
|
||||
} else {
|
||||
// All the nested classes are ignored.
|
||||
simpleName = String.valueOf(enclosingTypeNames[0]);
|
||||
}
|
||||
final String fullyQualifiedName = String.valueOf(packageName) + "." + simpleName;
|
||||
int relevance;
|
||||
if (Objects.equals(simpleName, this.typeName + "Test") ||
|
||||
Objects.equals(simpleName, this.typeName + "Tests")) {
|
||||
// mark this as most relevance
|
||||
relevance = Integer.MIN_VALUE;
|
||||
} else {
|
||||
relevance = simpleName.indexOf(this.typeName);
|
||||
if (relevance < 0) {
|
||||
// todo: better relevance calculation
|
||||
relevance = Integer.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
final boolean outOfBelongingProject;
|
||||
if (Objects.equals(this.javaProject.getElementName(), fullPath.segment(0))) {
|
||||
outOfBelongingProject = false;
|
||||
} else {
|
||||
outOfBelongingProject = true;
|
||||
}
|
||||
results.add(new TestNavigationItem(simpleName, fullyQualifiedName, uri, relevance, outOfBelongingProject));
|
||||
}
|
||||
}
|
||||
|
||||
static final class TestNavigationResult {
|
||||
public Collection<TestNavigationItem> items;
|
||||
public Location location;
|
||||
|
||||
public TestNavigationResult(Collection<TestNavigationItem> items, Location location) {
|
||||
this.items = items;
|
||||
this.location = location;
|
||||
}
|
||||
}
|
||||
|
||||
static final class TestNavigationItem {
|
||||
public String simpleName;
|
||||
public String fullyQualifiedName;
|
||||
public String uri;
|
||||
public int relevance;
|
||||
public boolean outOfBelongingProject;
|
||||
|
||||
public TestNavigationItem(String simpleName, String fullyQualifiedName, String uri,
|
||||
int relevance, boolean outOfBelongingProject) {
|
||||
this.simpleName = simpleName;
|
||||
this.fullyQualifiedName = fullyQualifiedName;
|
||||
this.uri = uri;
|
||||
this.relevance = relevance;
|
||||
this.outOfBelongingProject = outOfBelongingProject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((uri == null) ? 0 : uri.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final TestNavigationItem other = (TestNavigationItem) obj;
|
||||
if (uri == null) {
|
||||
if (other.uri != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!uri.equals(other.uri)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
152
package.json
152
package.json
|
@ -120,10 +120,26 @@
|
|||
{
|
||||
"command": "java.test.editor.debug",
|
||||
"when": "java:serverMode != LightWeight"
|
||||
},
|
||||
{
|
||||
"command": "java.test.goToTest",
|
||||
"when": "java:testRunnerActivated && resourceExtname == .java"
|
||||
}
|
||||
],
|
||||
"editor/context": [
|
||||
{
|
||||
"command": "java.test.goToTest",
|
||||
"when": "java:testRunnerActivated && resourcePath =~ /.*src[/|\\\\]main[/|\\\\]java[/|\\\\].*\\.java/",
|
||||
"group": "navigation@100"
|
||||
}
|
||||
]
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"command": "java.test.goToTest",
|
||||
"title": "%contributes.commands.java.test.goToTest.title%",
|
||||
"category": "Java"
|
||||
},
|
||||
{
|
||||
"command": "java.test.runFromJavaProjectExplorer",
|
||||
"title": "%contributes.commands.java.test.runFromJavaProjectExplorer.title%",
|
||||
|
@ -179,23 +195,23 @@
|
|||
"classPaths": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"$Auto",
|
||||
"$Runtime",
|
||||
"$Test",
|
||||
"!<path>"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"%configuration.java.test.config.classPaths.auto.description%",
|
||||
"%configuration.java.test.config.classPaths.runtime.description%",
|
||||
"%configuration.java.test.config.classPaths.test.description%",
|
||||
"%configuration.java.test.config.classPaths.exclude.description%"
|
||||
]
|
||||
},
|
||||
"string"
|
||||
]
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"$Auto",
|
||||
"$Runtime",
|
||||
"$Test",
|
||||
"!<path>"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"%configuration.java.test.config.classPaths.auto.description%",
|
||||
"%configuration.java.test.config.classPaths.runtime.description%",
|
||||
"%configuration.java.test.config.classPaths.test.description%",
|
||||
"%configuration.java.test.config.classPaths.exclude.description%"
|
||||
]
|
||||
},
|
||||
"string"
|
||||
]
|
||||
},
|
||||
"description": "%configuration.java.test.config.classPaths.description%",
|
||||
"default": []
|
||||
|
@ -203,23 +219,23 @@
|
|||
"modulePaths": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"$Auto",
|
||||
"$Runtime",
|
||||
"$Test",
|
||||
"!<path>"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"%configuration.java.test.config.modulePaths.auto.description%",
|
||||
"%configuration.java.test.config.modulePaths.runtime.description%",
|
||||
"%configuration.java.test.config.modulePaths.test.description%",
|
||||
"%configuration.java.test.config.modulePaths.exclude.description%"
|
||||
]
|
||||
},
|
||||
"string"
|
||||
]
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"$Auto",
|
||||
"$Runtime",
|
||||
"$Test",
|
||||
"!<path>"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"%configuration.java.test.config.modulePaths.auto.description%",
|
||||
"%configuration.java.test.config.modulePaths.runtime.description%",
|
||||
"%configuration.java.test.config.modulePaths.test.description%",
|
||||
"%configuration.java.test.config.modulePaths.exclude.description%"
|
||||
]
|
||||
},
|
||||
"string"
|
||||
]
|
||||
},
|
||||
"description": "%configuration.java.test.config.modulePaths.description%",
|
||||
"default": []
|
||||
|
@ -283,23 +299,23 @@
|
|||
"classPaths": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"$Auto",
|
||||
"$Runtime",
|
||||
"$Test",
|
||||
"!<path>"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"%configuration.java.test.config.classPaths.auto.description%",
|
||||
"%configuration.java.test.config.classPaths.runtime.description%",
|
||||
"%configuration.java.test.config.classPaths.test.description%",
|
||||
"%configuration.java.test.config.classPaths.exclude.description%"
|
||||
]
|
||||
},
|
||||
"string"
|
||||
]
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"$Auto",
|
||||
"$Runtime",
|
||||
"$Test",
|
||||
"!<path>"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"%configuration.java.test.config.classPaths.auto.description%",
|
||||
"%configuration.java.test.config.classPaths.runtime.description%",
|
||||
"%configuration.java.test.config.classPaths.test.description%",
|
||||
"%configuration.java.test.config.classPaths.exclude.description%"
|
||||
]
|
||||
},
|
||||
"string"
|
||||
]
|
||||
},
|
||||
"description": "%configuration.java.test.config.classPaths.description%",
|
||||
"default": []
|
||||
|
@ -307,23 +323,23 @@
|
|||
"modulePaths": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"$Auto",
|
||||
"$Runtime",
|
||||
"$Test",
|
||||
"!<path>"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"%configuration.java.test.config.modulePaths.auto.description%",
|
||||
"%configuration.java.test.config.modulePaths.runtime.description%",
|
||||
"%configuration.java.test.config.modulePaths.test.description%",
|
||||
"%configuration.java.test.config.modulePaths.exclude.description%"
|
||||
]
|
||||
},
|
||||
"string"
|
||||
]
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"$Auto",
|
||||
"$Runtime",
|
||||
"$Test",
|
||||
"!<path>"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"%configuration.java.test.config.modulePaths.auto.description%",
|
||||
"%configuration.java.test.config.modulePaths.runtime.description%",
|
||||
"%configuration.java.test.config.modulePaths.test.description%",
|
||||
"%configuration.java.test.config.modulePaths.exclude.description%"
|
||||
]
|
||||
},
|
||||
"string"
|
||||
]
|
||||
},
|
||||
"description": "%configuration.java.test.config.modulePaths.description%",
|
||||
"default": []
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
"contributes.commands.java.test.runFromJavaProjectExplorer.title": "Run Tests",
|
||||
"contributes.commands.java.test.debugFromJavaProjectExplorer.title": "Debug Tests",
|
||||
"contributes.commands.java.test.refreshExplorer.title": "Refresh",
|
||||
"contributes.commands.java.test.goToTest.title": "Go to Test",
|
||||
"configuration.java.test.defaultConfig.description": "Specify the name of the default test configuration",
|
||||
"configuration.java.test.config.description": "Specify the configurations for running the tests",
|
||||
"configuration.java.test.config.item.description": "Specify the configuration item for running the tests",
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
"contributes.commands.java.test.runFromJavaProjectExplorer.title": "运行测试",
|
||||
"contributes.commands.java.test.debugFromJavaProjectExplorer.title": "调试测试",
|
||||
"contributes.commands.java.test.refreshExplorer.title": "刷新",
|
||||
"contributes.commands.java.test.goToTest.title": "转到测试",
|
||||
"configuration.java.test.defaultConfig.description": "设定默认测试配置项的名称",
|
||||
"configuration.java.test.config.description": "设定运行测试的配置信息",
|
||||
"configuration.java.test.config.item.description": "设定运行测试时所用的配置项",
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { ExtensionContext, commands, window, Disposable, QuickPick, QuickInputButton, ThemeIcon, QuickPickItem } from 'vscode';
|
||||
import { JavaTestRunnerCommands } from '../constants';
|
||||
|
||||
export async function registerAskForChoiceCommand(context: ExtensionContext): Promise<void> {
|
||||
context.subscriptions.push(commands.registerCommand(JavaTestRunnerCommands.ASK_CLIENT_FOR_CHOICE, async (placeHolder: string, choices: IOption[], canPickMany: boolean) => {
|
||||
const ans: any = await window.showQuickPick(choices, {
|
||||
placeHolder,
|
||||
canPickMany,
|
||||
ignoreFocusOut: true,
|
||||
});
|
||||
|
||||
if (!ans) {
|
||||
return undefined;
|
||||
} else if (Array.isArray(ans)) {
|
||||
return ans.map((a: IOption) => a.value || a.label);
|
||||
}
|
||||
|
||||
return ans.value || ans.label;
|
||||
}));
|
||||
}
|
||||
|
||||
export async function registerAdvanceAskForChoice(context: ExtensionContext): Promise<void> {
|
||||
context.subscriptions.push(commands.registerCommand(JavaTestRunnerCommands.ADVANCED_ASK_CLIENT_FOR_CHOICE, async (placeHolder: string, choices: IOption[], advancedAction: string, canPickMany: boolean) => {
|
||||
let result: string[] | undefined;
|
||||
const disposables: Disposable[] = [];
|
||||
try {
|
||||
result = await new Promise<string[] | undefined>((resolve: (value: string[] | undefined) => void) => {
|
||||
const quickPick: QuickPick<IOption> = window.createQuickPick<IOption>();
|
||||
// if all the items are advanced item, show them directly
|
||||
let showAdvancedItem: boolean = choices.filter((c: IOption) => {
|
||||
return !c.isAdvanced;
|
||||
}).length === 0;
|
||||
quickPick.title = placeHolder;
|
||||
quickPick.placeholder = placeHolder;
|
||||
quickPick.items = filterOptions(showAdvancedItem, choices);
|
||||
quickPick.buttons = getActionButtons(showAdvancedItem, advancedAction);
|
||||
quickPick.canSelectMany = canPickMany;
|
||||
quickPick.ignoreFocusOut = true;
|
||||
disposables.push(quickPick.onDidTriggerButton((btn: QuickInputButton) => {
|
||||
if (btn.tooltip?.endsWith(advancedAction)) {
|
||||
showAdvancedItem = !showAdvancedItem;
|
||||
quickPick.items = filterOptions(showAdvancedItem, choices);
|
||||
quickPick.buttons = getActionButtons(showAdvancedItem, advancedAction);
|
||||
}
|
||||
}));
|
||||
disposables.push(quickPick.onDidHide(() => {
|
||||
return resolve(undefined);
|
||||
}));
|
||||
disposables.push(quickPick.onDidAccept(() => {
|
||||
return resolve(quickPick.selectedItems.map((o: IOption) => o.value));
|
||||
}));
|
||||
disposables.push(quickPick);
|
||||
quickPick.show();
|
||||
});
|
||||
} finally {
|
||||
for (const d of disposables) {
|
||||
d.dispose();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}));
|
||||
|
||||
function filterOptions(showAdvancedItem: boolean, choices: IOption[]): IOption[] {
|
||||
return choices.filter((c: IOption) => {
|
||||
return !c.isAdvanced || showAdvancedItem && c.isAdvanced;
|
||||
});
|
||||
}
|
||||
|
||||
function getActionButtons(showAdvancedItem: boolean, advancedAction: string): QuickInputButton[] {
|
||||
if (showAdvancedItem) {
|
||||
return [{
|
||||
iconPath: new ThemeIcon('collapse-all'),
|
||||
tooltip: `Hide ${advancedAction}`,
|
||||
}];
|
||||
}
|
||||
|
||||
return [{
|
||||
iconPath: new ThemeIcon('expand-all'),
|
||||
tooltip: `Show ${advancedAction}`,
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A command that server side can call to get a input value.
|
||||
* Currently it's only used to get a Java qualified name, so the check is on the client side.
|
||||
* @param context
|
||||
*/
|
||||
export async function registerAskForInputCommand(context: ExtensionContext): Promise<void> {
|
||||
context.subscriptions.push(commands.registerCommand(JavaTestRunnerCommands.ASK_CLIENT_FOR_INPUT, async (prompt: string, value: string) => {
|
||||
const ans: string | undefined = await window.showInputBox({
|
||||
value,
|
||||
prompt,
|
||||
validateInput: checkJavaQualifiedName,
|
||||
});
|
||||
return ans;
|
||||
}));
|
||||
}
|
||||
|
||||
function checkJavaQualifiedName(value: string): string {
|
||||
if (!value || !value.trim()) {
|
||||
return 'Input cannot be empty.';
|
||||
}
|
||||
|
||||
for (const part of value.split('.')) {
|
||||
if (isKeyword(part)) {
|
||||
return `Keyword '${part}' cannot be used.`;
|
||||
}
|
||||
|
||||
if (!isJavaIdentifier(part)) {
|
||||
return `Invalid Java qualified name.`;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
// Copied from https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-Keyword
|
||||
const keywords: Set<string> = new Set([
|
||||
'abstract', 'continue', 'for', 'new', 'switch',
|
||||
'assert', 'default', 'if', 'package', 'synchronized',
|
||||
'boolean', 'do', 'goto', 'private', 'this',
|
||||
'break', 'double', 'implements', 'protected', 'throw',
|
||||
'byte', 'else', 'import', 'public', 'throws',
|
||||
'case', 'enum', 'instanceof', 'return', 'transient',
|
||||
'catch', 'extends', 'int', 'short', 'try',
|
||||
'char', 'final', 'interface', 'static', 'void',
|
||||
'class', 'finally', 'long', 'strictfp', 'volatile',
|
||||
'const', 'float', 'native', 'super', 'while',
|
||||
]);
|
||||
export function isKeyword(identifier: string): boolean {
|
||||
return keywords.has(identifier);
|
||||
}
|
||||
|
||||
const identifierRegExp: RegExp = /^([a-zA-Z_$][a-zA-Z\d_$]*)$/;
|
||||
export function isJavaIdentifier(identifier: string): boolean {
|
||||
return identifierRegExp.test(identifier);
|
||||
}
|
||||
|
||||
export interface IOption extends QuickPickItem {
|
||||
value: string;
|
||||
isAdvanced?: boolean;
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { commands, Disposable, ExtensionContext, QuickInputButton, QuickPick, QuickPickItem, TextEdit, ThemeIcon, Uri, window, workspace, WorkspaceEdit } from 'vscode';
|
||||
import { TextEdit, Uri, window, workspace, WorkspaceEdit } from 'vscode';
|
||||
import * as protocolConverter from 'vscode-languageclient/lib/protocolConverter';
|
||||
import { JavaTestRunnerDelegateCommands } from '../constants';
|
||||
import { executeJavaLanguageServerCommand } from '../utils/commandUtils';
|
||||
|
@ -20,147 +20,6 @@ export async function generateTests(uri: Uri, cursorOffset: number): Promise<voi
|
|||
}
|
||||
}
|
||||
|
||||
export async function registerAskForChoiceCommand(context: ExtensionContext): Promise<void> {
|
||||
context.subscriptions.push(commands.registerCommand('_java.test.askClientForChoice', async (placeHolder: string, choices: IOption[], canPickMany: boolean) => {
|
||||
const ans: any = await window.showQuickPick(choices, {
|
||||
placeHolder,
|
||||
canPickMany,
|
||||
ignoreFocusOut: true,
|
||||
});
|
||||
|
||||
if (!ans) {
|
||||
return undefined;
|
||||
} else if (Array.isArray(ans)) {
|
||||
return ans.map((a: IOption) => a.value || a.label);
|
||||
}
|
||||
|
||||
return ans.value || ans.label;
|
||||
}));
|
||||
}
|
||||
|
||||
export async function registerAdvanceAskForChoice(context: ExtensionContext): Promise<void> {
|
||||
context.subscriptions.push(commands.registerCommand('_java.test.advancedAskClientForChoice', async (placeHolder: string, choices: IOption[], advancedAction: string, canPickMany: boolean) => {
|
||||
let result: string[] | undefined;
|
||||
const disposables: Disposable[] = [];
|
||||
try {
|
||||
result = await new Promise<string[] | undefined>((resolve: (value: string[] | undefined) => void) => {
|
||||
const quickPick: QuickPick<IOption> = window.createQuickPick<IOption>();
|
||||
// if all the items are advanced item, show them directly
|
||||
let showAdvancedItem: boolean = choices.filter((c: IOption) => {
|
||||
return !c.isAdvanced;
|
||||
}).length === 0;
|
||||
quickPick.title = placeHolder;
|
||||
quickPick.placeholder = placeHolder;
|
||||
quickPick.items = filterOptions(showAdvancedItem, choices);
|
||||
quickPick.buttons = getActionButtons(showAdvancedItem, advancedAction);
|
||||
quickPick.canSelectMany = canPickMany;
|
||||
quickPick.ignoreFocusOut = true;
|
||||
disposables.push(quickPick.onDidTriggerButton((btn: QuickInputButton) => {
|
||||
if (btn.tooltip?.endsWith(advancedAction)) {
|
||||
showAdvancedItem = !showAdvancedItem;
|
||||
quickPick.items = filterOptions(showAdvancedItem, choices);
|
||||
quickPick.buttons = getActionButtons(showAdvancedItem, advancedAction);
|
||||
}
|
||||
}));
|
||||
disposables.push(quickPick.onDidHide(() => {
|
||||
return resolve(undefined);
|
||||
}));
|
||||
disposables.push(quickPick.onDidAccept(() => {
|
||||
return resolve(quickPick.selectedItems.map((o: IOption) => o.value));
|
||||
}));
|
||||
disposables.push(quickPick);
|
||||
quickPick.show();
|
||||
});
|
||||
} finally {
|
||||
for (const d of disposables) {
|
||||
d.dispose();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}));
|
||||
|
||||
function filterOptions(showAdvancedItem: boolean, choices: IOption[]): IOption[] {
|
||||
return choices.filter((c: IOption) => {
|
||||
return !c.isAdvanced || showAdvancedItem && c.isAdvanced;
|
||||
});
|
||||
}
|
||||
|
||||
function getActionButtons(showAdvancedItem: boolean, advancedAction: string): QuickInputButton[] {
|
||||
if (showAdvancedItem) {
|
||||
return [{
|
||||
iconPath: new ThemeIcon('collapse-all'),
|
||||
tooltip: `Hide ${advancedAction}`,
|
||||
}];
|
||||
}
|
||||
|
||||
return [{
|
||||
iconPath: new ThemeIcon('expand-all'),
|
||||
tooltip: `Show ${advancedAction}`,
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A command that server side can call to get a input value.
|
||||
* Currently it's only used to get a Java qualified name, so the check is on the client side.
|
||||
* @param context
|
||||
*/
|
||||
export async function registerAskForInputCommand(context: ExtensionContext): Promise<void> {
|
||||
context.subscriptions.push(commands.registerCommand('_java.test.askClientForInput', async (prompt: string, value: string) => {
|
||||
const ans: string | undefined = await window.showInputBox({
|
||||
value,
|
||||
prompt,
|
||||
validateInput: checkJavaQualifiedName,
|
||||
});
|
||||
return ans;
|
||||
}));
|
||||
}
|
||||
|
||||
async function askServerToGenerateTests(uri: Uri, cursorOffset: number): Promise<any> {
|
||||
return await executeJavaLanguageServerCommand<any>(JavaTestRunnerDelegateCommands.GENERATE_TESTS, uri.toString(), cursorOffset);
|
||||
}
|
||||
|
||||
function checkJavaQualifiedName(value: string): string {
|
||||
if (!value || !value.trim()) {
|
||||
return 'Input cannot be empty.';
|
||||
}
|
||||
|
||||
for (const part of value.split('.')) {
|
||||
if (isKeyword(part)) {
|
||||
return `Keyword '${part}' cannot be used.`;
|
||||
}
|
||||
|
||||
if (!isJavaIdentifier(part)) {
|
||||
return `Invalid Java qualified name.`;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
// Copied from https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-Keyword
|
||||
const keywords: Set<string> = new Set([
|
||||
'abstract', 'continue', 'for', 'new', 'switch',
|
||||
'assert', 'default', 'if', 'package', 'synchronized',
|
||||
'boolean', 'do', 'goto', 'private', 'this',
|
||||
'break', 'double', 'implements', 'protected', 'throw',
|
||||
'byte', 'else', 'import', 'public', 'throws',
|
||||
'case', 'enum', 'instanceof', 'return', 'transient',
|
||||
'catch', 'extends', 'int', 'short', 'try',
|
||||
'char', 'final', 'interface', 'static', 'void',
|
||||
'class', 'finally', 'long', 'strictfp', 'volatile',
|
||||
'const', 'float', 'native', 'super', 'while',
|
||||
]);
|
||||
export function isKeyword(identifier: string): boolean {
|
||||
return keywords.has(identifier);
|
||||
}
|
||||
|
||||
const identifierRegExp: RegExp = /^([a-zA-Z_$][a-zA-Z\d_$]*)$/;
|
||||
export function isJavaIdentifier(identifier: string): boolean {
|
||||
return identifierRegExp.test(identifier);
|
||||
}
|
||||
|
||||
interface IOption extends QuickPickItem {
|
||||
value: string;
|
||||
isAdvanced?: boolean;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { Uri, Position, Location } from 'vscode';
|
||||
import { SymbolItemNavigation } from '../../references-view';
|
||||
import { ITestNavigationItem } from './navigationCommands';
|
||||
|
||||
export class TestNavigationModel implements SymbolItemNavigation<ITestNavigationItem> {
|
||||
nearest(): ITestNavigationItem | undefined {
|
||||
return undefined
|
||||
}
|
||||
|
||||
next(from: ITestNavigationItem): ITestNavigationItem {
|
||||
return from;
|
||||
}
|
||||
|
||||
previous(from: ITestNavigationItem): ITestNavigationItem {
|
||||
return from;
|
||||
}
|
||||
|
||||
location(item: ITestNavigationItem): Location | undefined {
|
||||
return new Location(Uri.file(item.uri), new Position(0, 0));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { ProviderResult, TreeDataProvider, TreeItem, Uri } from 'vscode';
|
||||
import { ITestNavigationItem } from './navigationCommands';
|
||||
|
||||
export class TestNavigationTreeDataProvider implements TreeDataProvider<ITestNavigationItem> {
|
||||
items: ITestNavigationItem[]
|
||||
|
||||
constructor(items: ITestNavigationItem[]) {
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
getTreeItem(element: ITestNavigationItem): TreeItem | Thenable<TreeItem> {
|
||||
const treeItem: TreeItem = new TreeItem(element.simpleName);
|
||||
treeItem.resourceUri = Uri.file(element.uri);
|
||||
treeItem.description = element.fullyQualifiedName;
|
||||
treeItem.command = {
|
||||
command: 'vscode.open',
|
||||
title: 'Open Type Location',
|
||||
arguments: [
|
||||
Uri.parse(element.uri)
|
||||
]
|
||||
}
|
||||
return treeItem;
|
||||
}
|
||||
|
||||
getChildren(element?: ITestNavigationItem): ProviderResult<ITestNavigationItem[]> {
|
||||
if (!element) {
|
||||
return this.items;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import * as path from 'path';
|
||||
import { commands, extensions, Location, Range, Uri, window } from 'vscode';
|
||||
import { JavaTestRunnerCommands, JavaTestRunnerDelegateCommands, VSCodeCommands } from '../../constants';
|
||||
import { testSourceProvider } from '../../provider/testSourceProvider';
|
||||
import { SymbolTree } from '../../references-view';
|
||||
import { executeJavaLanguageServerCommand } from '../../utils/commandUtils';
|
||||
import { IOption } from '../askForOptionCommands';
|
||||
import { TestNavigationInput } from './testNavigationInput';
|
||||
|
||||
const GENERATE_TESTS: string = 'Generate tests...';
|
||||
const SEARCH_TEST_FILES: string = 'Search test files...';
|
||||
const REFERENCES_VIEW_EXTENSION: string = 'ms-vscode.references-view';
|
||||
|
||||
export async function goToTest(): Promise<void> {
|
||||
if (!window.activeTextEditor) {
|
||||
return;
|
||||
}
|
||||
const uri: Uri = window.activeTextEditor.document.uri;
|
||||
if (await testSourceProvider.isOnTestSourcePath(uri)) {
|
||||
return;
|
||||
}
|
||||
const result: ITestNavigationResult | undefined = await searchTests(uri.toString());
|
||||
if (!result?.items?.length) {
|
||||
window.showQuickPick([
|
||||
GENERATE_TESTS,
|
||||
SEARCH_TEST_FILES,
|
||||
], {
|
||||
placeHolder: 'No tests found for current source file'
|
||||
}).then((choice: string | undefined) => {
|
||||
if (choice === SEARCH_TEST_FILES) {
|
||||
const fileName: string = path.basename(window.activeTextEditor!.document.fileName);
|
||||
commands.executeCommand(VSCodeCommands.WORKBENCH_ACTION_QUICK_OPEN, fileName.substring(0, fileName.lastIndexOf('.')));
|
||||
} else if (choice === GENERATE_TESTS) {
|
||||
commands.executeCommand(JavaTestRunnerCommands.JAVA_TEST_GENERATE_TESTS, uri, 0);
|
||||
}
|
||||
});
|
||||
} else if (result.items.length === 1) {
|
||||
window.showTextDocument(Uri.parse(result.items[0].uri));
|
||||
} else {
|
||||
const sortedResults: ITestNavigationItem[] = result.items.sort((a: ITestNavigationItem, b: ITestNavigationItem) => {
|
||||
if (a.outOfBelongingProject && !b.outOfBelongingProject) {
|
||||
return Number.MAX_SAFE_INTEGER;
|
||||
} else if (!a.outOfBelongingProject && b.outOfBelongingProject) {
|
||||
return Number.MIN_SAFE_INTEGER;
|
||||
} else {
|
||||
if (a.relevance === b.relevance) {
|
||||
return a.simpleName.localeCompare(b.simpleName);
|
||||
}
|
||||
return a.relevance - b.relevance;
|
||||
}
|
||||
});
|
||||
const api: SymbolTree | undefined = await extensions.getExtension<SymbolTree>(REFERENCES_VIEW_EXTENSION)?.activate();
|
||||
if (api) {
|
||||
const input: TestNavigationInput = new TestNavigationInput(
|
||||
'Related Tests',
|
||||
new Location(uri, new Range(
|
||||
result.location.range.start.line,
|
||||
result.location.range.start.character,
|
||||
result.location.range.end.line,
|
||||
result.location.range.end.line,
|
||||
)),
|
||||
sortedResults
|
||||
);
|
||||
api.setInput(input);
|
||||
} else {
|
||||
goToTestFallback(sortedResults);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function goToTestFallback(results: ITestNavigationItem[]): Promise<void> {
|
||||
const items: IOption[] = results.map((r: ITestNavigationItem) => {
|
||||
return {
|
||||
label: r.simpleName,
|
||||
detail: r.fullyQualifiedName,
|
||||
value: r.uri,
|
||||
isAdvanced: r.outOfBelongingProject,
|
||||
};
|
||||
});
|
||||
const choice: string[] | undefined = await commands.executeCommand(
|
||||
JavaTestRunnerCommands.ADVANCED_ASK_CLIENT_FOR_CHOICE,
|
||||
'Choose a test class to open',
|
||||
items,
|
||||
'tests in other projects',
|
||||
false,
|
||||
);
|
||||
if (choice?.length) {
|
||||
window.showTextDocument(Uri.parse(choice[0]));
|
||||
}
|
||||
}
|
||||
|
||||
async function searchTests(uri: string): Promise<ITestNavigationResult | undefined> {
|
||||
return await executeJavaLanguageServerCommand<ITestNavigationResult | undefined>(
|
||||
JavaTestRunnerDelegateCommands.NAVIGATE_TO_TEST_OR_TARGET, uri, true);
|
||||
}
|
||||
|
||||
export interface ITestNavigationResult {
|
||||
items: ITestNavigationItem[];
|
||||
location: Location;
|
||||
}
|
||||
|
||||
export interface ITestNavigationItem {
|
||||
simpleName: string;
|
||||
fullyQualifiedName: string;
|
||||
uri: string;
|
||||
relevance: number;
|
||||
outOfBelongingProject: boolean;
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { Location, ProviderResult } from 'vscode';
|
||||
import { SymbolTreeInput, SymbolTreeModel } from '../../references-view';
|
||||
import { ITestNavigationItem } from './navigationCommands';
|
||||
import { TestNavigationTreeDataProvider } from './TestNavigationTreeDataProvider';
|
||||
|
||||
export class TestNavigationInput implements SymbolTreeInput<ITestNavigationItem> {
|
||||
readonly title: string;
|
||||
readonly location: Location;
|
||||
readonly contextValue: string = 'javaTestNavigation';
|
||||
items: ITestNavigationItem[];
|
||||
|
||||
|
||||
constructor(title: string, location: Location, items: ITestNavigationItem[]) {
|
||||
this.title = title;
|
||||
this.location = location;
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
resolve(): ProviderResult<SymbolTreeModel<ITestNavigationItem>> {
|
||||
const provider: TestNavigationTreeDataProvider = new TestNavigationTreeDataProvider(this.items);
|
||||
const treeModel: SymbolTreeModel<ITestNavigationItem> = {
|
||||
message: undefined,
|
||||
provider,
|
||||
};
|
||||
return treeModel;
|
||||
}
|
||||
|
||||
with(location: Location): SymbolTreeInput<ITestNavigationItem> {
|
||||
return new TestNavigationInput(this.title, location, this.items);
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ export namespace JavaTestRunnerDelegateCommands {
|
|||
export const FIND_DIRECT_CHILDREN_FOR_CLASS: string = 'vscode.java.test.findDirectTestChildrenForClass';
|
||||
export const FIND_TEST_TYPES_AND_METHODS: string = 'vscode.java.test.findTestTypesAndMethods';
|
||||
export const RESOLVE_PATH: string = 'vscode.java.test.resolvePath';
|
||||
export const NAVIGATE_TO_TEST_OR_TARGET: string = 'vscode.java.test.navigateToTestOrTarget';
|
||||
}
|
||||
|
||||
export namespace JavaTestRunnerCommands {
|
||||
|
@ -27,12 +28,17 @@ export namespace JavaTestRunnerCommands {
|
|||
export const REFRESH_TEST_EXPLORER: string = 'java.test.refreshExplorer';
|
||||
export const JAVA_TEST_GENERATE_TESTS: string = 'java.test.generateTests';
|
||||
export const FIND_TEST_LOCATION: string = 'vscode.java.test.findTestLocation';
|
||||
export const GO_TO_TEST: string = 'java.test.goToTest';
|
||||
export const JAVA_TEST_OPEN_STACKTRACE: string = '_java.test.openStackTrace';
|
||||
export const ASK_CLIENT_FOR_CHOICE: string = '_java.test.askClientForChoice';
|
||||
export const ASK_CLIENT_FOR_INPUT: string = '_java.test.askClientForInput'
|
||||
export const ADVANCED_ASK_CLIENT_FOR_CHOICE: string = '_java.test.advancedAskClientForChoice';
|
||||
}
|
||||
|
||||
export namespace VSCodeCommands {
|
||||
export const RUN_TESTS_IN_CURRENT_FILE: string = 'testing.runCurrentFile';
|
||||
export const DEBUG_TESTS_IN_CURRENT_FILE: string = 'testing.debugCurrentFile';
|
||||
export const WORKBENCH_ACTION_QUICK_OPEN: string = 'workbench.action.quickOpen';
|
||||
}
|
||||
|
||||
export namespace Configurations {
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
import * as path from 'path';
|
||||
import { commands, DebugConfiguration, Event, Extension, ExtensionContext, extensions, TestItem, TextDocument, TextDocumentChangeEvent, TextEditor, Uri, window, workspace, WorkspaceFoldersChangeEvent } from 'vscode';
|
||||
import { dispose as disposeTelemetryWrapper, initializeFromJsonFile, instrumentOperation, instrumentOperationAsVsCodeCommand } from 'vscode-extension-telemetry-wrapper';
|
||||
import { generateTests, registerAdvanceAskForChoice, registerAskForChoiceCommand, registerAskForInputCommand } from './commands/generationCommands';
|
||||
import { goToTest } from './commands/navigation/navigationCommands';
|
||||
import { generateTests } from './commands/generationCommands';
|
||||
import { runTestsFromJavaProjectExplorer } from './commands/projectExplorerCommands';
|
||||
import { refresh, runTestsFromTestExplorer } from './commands/testExplorerCommands';
|
||||
import { openStackTrace } from './commands/testReportCommands';
|
||||
|
@ -15,6 +16,7 @@ import { IProgressProvider } from './debugger.api';
|
|||
import { initExpService } from './experimentationService';
|
||||
import { disposeCodeActionProvider, registerTestCodeActionProvider } from './provider/codeActionProvider';
|
||||
import { testSourceProvider } from './provider/testSourceProvider';
|
||||
import { registerAskForChoiceCommand, registerAdvanceAskForChoice, registerAskForInputCommand } from './commands/askForOptionCommands';
|
||||
|
||||
export let extensionContext: ExtensionContext;
|
||||
|
||||
|
@ -98,6 +100,7 @@ async function doActivate(_operationId: string, context: ExtensionContext): Prom
|
|||
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.REFRESH_TEST_EXPLORER, async () => await refresh()),
|
||||
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.RUN_TEST_FROM_JAVA_PROJECT_EXPLORER, async (node: any) => await runTestsFromJavaProjectExplorer(node, false /* isDebug */)),
|
||||
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.DEBUG_TEST_FROM_JAVA_PROJECT_EXPLORER, async (node: any) => await runTestsFromJavaProjectExplorer(node, true /* isDebug */)),
|
||||
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.GO_TO_TEST, async () => await goToTest()),
|
||||
window.onDidChangeActiveTextEditor(async (e: TextEditor | undefined) => {
|
||||
if (await isTestJavaFile(e?.document)) {
|
||||
await updateItemForDocumentWithDebounce(e!.document.uri);
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
/**
|
||||
* This interface describes the shape for the references viewlet API. It consists
|
||||
* of a single `setInput` function which must be called with a full implementation
|
||||
* of the `SymbolTreeInput`-interface. To acquire this API use the default mechanics, e.g:
|
||||
*
|
||||
* ```ts
|
||||
* // get references viewlet API
|
||||
* const api = await vscode.extensions.getExtension<SymbolTree>('ms-vscode.references-view').activate();
|
||||
*
|
||||
* // instantiate and set input which updates the view
|
||||
* const myInput: SymbolTreeInput<MyItems> = ...
|
||||
* api.setInput(myInput)
|
||||
* ```
|
||||
*/
|
||||
export interface SymbolTree {
|
||||
|
||||
/**
|
||||
* Set the contents of the references viewlet.
|
||||
*
|
||||
* @param input A symbol tree input object
|
||||
*/
|
||||
setInput(input: SymbolTreeInput<unknown>): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A symbol tree input is the entry point for populating the references viewlet.
|
||||
* Inputs must be anchored at a code location, they must have a title, and they
|
||||
* must resolve to a model.
|
||||
*/
|
||||
export interface SymbolTreeInput<T> {
|
||||
|
||||
/**
|
||||
* The value of the `reference-list.source` context key. Use this to control
|
||||
* input dependent commands.
|
||||
*/
|
||||
readonly contextValue: string;
|
||||
|
||||
/**
|
||||
* The (short) title of this input, like "Implementations" or "Callers Of"
|
||||
*/
|
||||
readonly title: string;
|
||||
|
||||
/**
|
||||
* The location at which this position is anchored. Locations are validated and inputs
|
||||
* with "funny" locations might be ignored
|
||||
*/
|
||||
readonly location: vscode.Location;
|
||||
|
||||
/**
|
||||
* Resolve this input to a model that contains the actual data. When there are no result
|
||||
* than `undefined` or `null` should be returned.
|
||||
*/
|
||||
resolve(): vscode.ProviderResult<SymbolTreeModel<T>>;
|
||||
|
||||
/**
|
||||
* This function is called when re-running from history. The symbols tree has tracked
|
||||
* the original location of this input and that is now passed to this input. The
|
||||
* implementation of this function should return a clone where the `location`-property
|
||||
* uses the provided `location`
|
||||
*
|
||||
* @param location The location at which the new input should be anchored.
|
||||
* @returns A new input which location is anchored at the position.
|
||||
*/
|
||||
with(location: vscode.Location): SymbolTreeInput<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A symbol tree model which is used to populate the symbols tree.
|
||||
*/
|
||||
export interface SymbolTreeModel<T> {
|
||||
|
||||
/**
|
||||
* A tree data provider which is used to populate the symbols tree.
|
||||
*/
|
||||
provider: vscode.TreeDataProvider<T>;
|
||||
|
||||
/**
|
||||
* An optional message that is displayed above the tree. Whenever the provider
|
||||
* fires a change event this message is read again.
|
||||
*/
|
||||
message: string | undefined;
|
||||
|
||||
/**
|
||||
* Optional support for symbol navigation. When implemented, navigation commands like
|
||||
* "Go to Next" and "Go to Previous" will be working with this model.
|
||||
*/
|
||||
navigation?: SymbolItemNavigation<T>;
|
||||
|
||||
/**
|
||||
* Optional support for editor highlights. WHen implemented, the editor will highlight
|
||||
* symbol ranges in the source code.
|
||||
*/
|
||||
highlights?: SymbolItemEditorHighlights<T>;
|
||||
|
||||
/**
|
||||
* Optional dispose function which is invoked when this model is
|
||||
* needed anymore
|
||||
*/
|
||||
dispose?(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to support the built-in symbol navigation.
|
||||
*/
|
||||
export interface SymbolItemNavigation<T> {
|
||||
/**
|
||||
* Return the item that is the nearest to the given location or `undefined`
|
||||
*/
|
||||
nearest(uri: vscode.Uri, position: vscode.Position): T | undefined;
|
||||
/**
|
||||
* Return the next item from the given item or the item itself.
|
||||
*/
|
||||
next(from: T): T;
|
||||
/**
|
||||
* Return the previous item from the given item or the item itself.
|
||||
*/
|
||||
previous(from: T): T;
|
||||
/**
|
||||
* Return the location of the given item.
|
||||
*/
|
||||
location(item: T): vscode.Location | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to support the built-in editor highlights.
|
||||
*/
|
||||
export interface SymbolItemEditorHighlights<T> {
|
||||
/**
|
||||
* Given an item and an uri return an array of ranges to highlight.
|
||||
*/
|
||||
getEditorHighlights(item: T, uri: vscode.Uri): vscode.Range[] | undefined;
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
'use strict';
|
||||
|
||||
import assert = require('assert');
|
||||
import * as path from 'path';
|
||||
import { Uri, window } from 'vscode';
|
||||
import { ITestNavigationResult } from '../../src/commands/navigation/navigationCommands';
|
||||
import { JavaTestRunnerDelegateCommands } from '../../src/constants';
|
||||
import { executeJavaLanguageServerCommand } from '../../src/utils/commandUtils';
|
||||
import { setupTestEnv } from "./utils";
|
||||
|
||||
// tslint:disable: only-arrow-functions
|
||||
// tslint:disable: no-object-literal-type-assertion
|
||||
const PROJECT_PATH: string = path.join(__dirname, '../../..', 'test', 'test-projects','junit');
|
||||
suite('Test Navigation Tests', () => {
|
||||
|
||||
suiteSetup(async function() {
|
||||
await setupTestEnv();
|
||||
});
|
||||
|
||||
test('test go to test', async () => {
|
||||
const filePath: string = path.join(PROJECT_PATH, 'src', 'main', 'java', 'junit', 'App.java');
|
||||
await window.showTextDocument(Uri.file(filePath));
|
||||
const uri: Uri = window.activeTextEditor!.document.uri;
|
||||
const searchResult = await executeJavaLanguageServerCommand<ITestNavigationResult>(
|
||||
JavaTestRunnerDelegateCommands.NAVIGATE_TO_TEST_OR_TARGET, uri.toString(), true);
|
||||
assert.strictEqual(searchResult?.items.length, 1);
|
||||
assert.strictEqual(searchResult?.items[0].simpleName, 'AppTest');
|
||||
assert.strictEqual(searchResult?.items[0].fullyQualifiedName, 'junit5.AppTest');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
package junit5;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class AppTest {
|
||||
@Test
|
||||
void testGetGreeting() {
|
||||
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче