diff --git a/ide/gototest/apichanges.xml b/ide/gototest/apichanges.xml new file mode 100644 index 000000000000..7ae8b88aba2b --- /dev/null +++ b/ide/gototest/apichanges.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + Navigate To Test APIs + + + + + + Adding TestOppositeLocator + + + + + + A TestOppsiteLocator class introduced that finds one or multiple test files for a source file, and one or multiple source files for a test file. + + + + + + + + + + + Navigate To Test API changes by date + + + + + + +

Introduction

+ +

This document lists changes made to the Navigate To Test APIs. Please ask on the + dev@netbeans.apache.org + mailing list if you have any questions about the details of a + change, or are wondering how to convert existing code to be compatible. +

+ +
+ +

@FOOTER@

+ + +
+
diff --git a/ide/gototest/nbproject/project.xml b/ide/gototest/nbproject/project.xml index 47a0a1c6e4de..a25850f94569 100644 --- a/ide/gototest/nbproject/project.xml +++ b/ide/gototest/nbproject/project.xml @@ -25,6 +25,15 @@ org.netbeans.modules.gototest + + org.netbeans.api.annotations.common + + + + 1 + 1.50 + + org.netbeans.modules.gsf.testrunner @@ -100,7 +109,7 @@ - org.openide.util.ui + org.openide.util @@ -108,19 +117,19 @@ - org.openide.util + org.openide.util.lookup - 9.3 + 8.0 - org.openide.util.lookup + org.openide.util.ui - 8.0 + 9.3 @@ -135,6 +144,7 @@ org.netbeans.modules.jackpot30.file org.netbeans.modules.java.hints.declarative + org.netbeans.modules.java.lsp.server org.netbeans.modules.junit org.netbeans.modules.php.project org.netbeans.modules.python.project @@ -142,6 +152,7 @@ org.netbeans.modules.ruby.project org.netbeans.modules.testng org.netbeans.modules.web.clientproject.api + org.netbeans.api.gototest org.netbeans.spi.gototest diff --git a/ide/gototest/src/org/netbeans/api/gototest/TestOppositesLocator.java b/ide/gototest/src/org/netbeans/api/gototest/TestOppositesLocator.java new file mode 100644 index 000000000000..c1d4db4f0ffd --- /dev/null +++ b/ide/gototest/src/org/netbeans/api/gototest/TestOppositesLocator.java @@ -0,0 +1,268 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.api.gototest; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.spi.gototest.TestLocator; +import org.netbeans.spi.gototest.TestLocator.LocationListener; +import org.netbeans.spi.gototest.TestLocator.LocationResult; +import org.openide.filesystems.FileObject; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; +import org.openide.util.RequestProcessor; + +/** + * Find one or multiple test files for a source file, + * and one or multiple source files for a test file. + * + * @since 1.57 + */ +public final class TestOppositesLocator { + + private static final RequestProcessor WORKER = new RequestProcessor(TestOppositesLocator.class.getName(), 1, false, false); + + /** + * The default instance of TestOppositesLocator. + * + * @return the default instance of TestOppositesLocator + */ + public static TestOppositesLocator getDefault() { + return new TestOppositesLocator(); + } + + private TestOppositesLocator() {} + + /** + * Given the file and position in the file, if the: + * + * + * @param fo the file for which the opposites should be found + * @param caretOffset position in the file, or {@code -1} if unknown + * @return a result describing either an error, or a possibly empty list of locations found; + * note one of {@code errorMessage} and {@code locations} is always {@code null}, + * and one always non-{@code null}. + */ + @NbBundle.Messages("No_Test_Or_Tested_Class_Found=No Test or Tested class found") + public CompletableFuture findOpposites(FileObject fo, int caretOffset) { + if (!isSupportedFileType(fo)) { + CompletableFuture result = new CompletableFuture<>(); + + result.complete(new LocatorResult(Bundle.No_Test_Or_Tested_Class_Found(), null, null)); + return result; + } + else { + return populateLocationResults(fo, caretOffset) + .thenApply(locations -> new LocatorResult(null, + locations.stream() + .filter(l -> l.getErrorMessage() != null) + .map(l -> l.getErrorMessage()) + .collect(Collectors.toList()), + locations.stream() + .filter(l -> l.getFileObject()!= null) + .map(l -> new Location(l.getFileObject(), l.getOffset())) + .collect(Collectors.toList()))); + } + } + + private CompletableFuture> populateLocationResults(FileObject fo, int caretOffset) { + Collection locators = Lookup.getDefault() + .lookupAll(TestLocator.class) + .stream() + .filter(tl -> tl.appliesTo(fo)) + .collect(Collectors.toList()); + CompletableFuture> result = new CompletableFuture<>(); + + result.complete(new ArrayList<>()); + + for (TestLocator locator : locators) { + if (locator.appliesTo(fo)) { + CompletableFuture> currentFuture = new CompletableFuture<>(); + + if (locator.asynchronous()) { + locator.findOpposite(fo, caretOffset, new LocationListener() { + @Override + public void foundLocation(FileObject fo, LocationResult location) { + List resultList = + location != null ? Collections.singletonList(location) + : Collections.emptyList(); + + currentFuture.complete(resultList); + } + }); + } else { + WORKER.post(() -> { + try { + LocationResult opposite = locator.findOpposite(fo, caretOffset); + List resultList = + opposite != null ? Collections.singletonList(opposite) + : Collections.emptyList(); + + currentFuture.complete(resultList); + }catch(ThreadDeath td){ + throw td; + }catch (Throwable t) { + currentFuture.completeExceptionally(t); + } + }); + } + + result = result.thenCombine(currentFuture, (accumulator, currentList) -> { + accumulator.addAll(currentList); + return accumulator; + }); + } + } + + return result; + } + + private TestLocator getLocatorFor(FileObject fo) { + Collection locators = Lookup.getDefault().lookupAll(TestLocator.class); + for (TestLocator locator : locators) { + if (locator.appliesTo(fo)) { + return locator; + } + } + + return null; + } + + private boolean isSupportedFileType(FileObject fo) { + TestLocator locator = fo != null ? getLocatorFor(fo) : null; + if (locator != null) { + return locator.getFileType(fo) != TestLocator.FileType.NEITHER; + } + + return false; + } + + /** + * A description of the found opposite files. Exactly one of {@code errorMessage} + * {@code locations} will be non-null; + */ + public static final class LocatorResult { + private final String errorMessage; + private final Collection providerErrors; + private final Collection locations; + + private LocatorResult(String errorMessage, + List providerErrors, + List locations) { + if (errorMessage == null && locations == null) { + throw new IllegalArgumentException("Both errorMessage and locations is null!"); + } + if (errorMessage != null && locations != null) { + throw new IllegalArgumentException("Both errorMessage and locations is non-null!"); + } + if (providerErrors == null ^ locations == null) { + throw new IllegalArgumentException("Both providerErrors and locations must either be null or non-null"); + } + this.errorMessage = errorMessage; + this.providerErrors = providerErrors != null ? Collections.unmodifiableList(providerErrors) : null; + this.locations = locations != null ? Collections.unmodifiableList(locations) : null; + } + + /** + * Get the error message if present. + * + * @return error message + */ + public @CheckForNull String getErrorMessage() { + return errorMessage; + } + + /** + * Get error messages provided by the providers. + * + * @return the errors from the providers. + */ + public @CheckForNull Collection getProviderErrors() { + return providerErrors; + } + + /** + * Get the locations if present. + * + * @return the found locations. + */ + public @CheckForNull Collection getLocations() { + return locations; + } + + } + + /** + * A description of a target location. + */ + public static final class Location { + private final FileObject file; + private final int offset; + + /** + * Construct a Location from a given file and offset. + * @param file The FileObject of the opposite file. + * @param offset The offset in the file, or -1 if the offset + * is unknown. + */ + public Location(FileObject file, int offset) { + this.file = file; + this.offset = offset; + } + + /** + * Get the FileObject associated with this location + * @return The FileObject for this location, or null if + * this is an invalid location. In that case, consult + * {@link #getErrorMessage} for more information. + */ + public FileObject getFileObject() { + return file; + } + + /** + * Get the offset associated with this location, if any. + * @return The offset for this location, or -1 if the offset + * is not known. + */ + public int getOffset() { + return offset; + } + + /** + * Get the proper display name for this location. + * + * @return the display name for this location + */ + @Messages("DN_Error=Error") + public String getDisplayName() { + return file != null ? file.getName() : Bundle.DN_Error(); + } + } + +} \ No newline at end of file diff --git a/ide/gototest/src/org/netbeans/modules/gototest/GotoOppositeAction.java b/ide/gototest/src/org/netbeans/modules/gototest/GotoOppositeAction.java index cee8765c6698..b2f7f7aabe35 100644 --- a/ide/gototest/src/org/netbeans/modules/gototest/GotoOppositeAction.java +++ b/ide/gototest/src/org/netbeans/modules/gototest/GotoOppositeAction.java @@ -23,22 +23,23 @@ import java.awt.Point; import java.awt.Rectangle; import java.util.Collection; -import java.util.HashMap; -import java.util.concurrent.Semaphore; +import java.util.List; +import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; import javax.swing.Action; import javax.swing.JEditorPane; import javax.swing.SwingUtilities; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.JTextComponent; +import org.netbeans.api.gototest.TestOppositesLocator; +import org.netbeans.api.gototest.TestOppositesLocator.Location; +import org.netbeans.api.gototest.TestOppositesLocator.LocatorResult; import org.netbeans.modules.gsf.testrunner.ui.api.TestCreatorPanelDisplayer; import org.netbeans.modules.gsf.testrunner.ui.api.UICommonUtils; import org.netbeans.modules.parsing.api.Source; -import org.netbeans.spi.gototest.TestLocator; -import org.netbeans.spi.gototest.TestLocator.FileType; -import org.netbeans.spi.gototest.TestLocator.LocationListener; import org.netbeans.spi.gototest.TestLocator.LocationResult; import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; @@ -64,8 +65,6 @@ * @author Tor Norbye */ public class GotoOppositeAction extends CallableSystemAction { - private HashMap locationResults = new HashMap(); - private Semaphore lock; public GotoOppositeAction() { putValue("noIconInMenu", Boolean.TRUE); //NOI18N @@ -113,7 +112,6 @@ protected boolean asynchronous() { } @Override - @NbBundle.Messages("No_Test_Or_Tested_Class_Found=No Test or Tested class found") public void performAction() { int caretOffsetHolder[] = { -1 }; final FileObject fo = getApplicableFileObject(caretOffsetHolder); @@ -126,26 +124,38 @@ public void performAction() { @Override public void run() { - FileType currentFileType = getCurrentFileType(); - if(currentFileType == FileType.NEITHER) { - StatusDisplayer.getDefault().setStatusText(Bundle.No_Test_Or_Tested_Class_Found()); + LocatorResult opposites; + + try { + opposites = TestOppositesLocator.getDefault().findOpposites(fo, caretOffset).get(); + } catch (InterruptedException | ExecutionException ex) { + Exceptions.printStackTrace(ex); + return ; + } + + if (opposites.getErrorMessage() != null) { + StatusDisplayer.getDefault().setStatusText(opposites.getErrorMessage()); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { TestCreatorPanelDisplayer.getDefault().displayPanel(UICommonUtils.getFileObjectsFromNodes(TopComponent.getRegistry().getActivatedNodes()), null, null); } }); - } - else { - populateLocationResults(fo, caretOffset); + } else { + opposites.getProviderErrors() + .stream() + .forEach(msg -> { + DialogDisplayer.getDefault().notify( + new NotifyDescriptor.Message(msg, NotifyDescriptor.INFORMATION_MESSAGE)); + }); + Collection locations = opposites.getLocations(); SwingUtilities.invokeLater(new Runnable() { - @Override public void run() { - if (locationResults.size() == 1) { - handleResult(locationResults.keySet().iterator().next()); - } else if (locationResults.size() > 1) { - showPopup(fo); + if (locations.size() == 1) { + handleResult(locations.iterator().next()); + } else if (locations.size() > 1) { + showPopup(fo, locations); } } }); @@ -155,82 +165,8 @@ public void run() { } } - private void populateLocationResults(FileObject fo, int caretOffset) { - locationResults.clear(); - - Collection locators = Lookup.getDefault().lookupAll(TestLocator.class); - - int permits = 0; - for (TestLocator locator : locators) { - if (locator.appliesTo(fo)) { - permits++; - } - } - - lock = new Semaphore(permits); - try { - lock.acquire(permits); - } catch (InterruptedException e) { - } - - for (TestLocator locator : locators) { - if (locator.appliesTo(fo)) { - doPopulateLocationResults(fo, caretOffset, locator); - } - } - try { - lock.acquire(permits); - } catch (InterruptedException ex) { - Exceptions.printStackTrace(ex); - } - } - - private void doPopulateLocationResults(FileObject fo, int caretOffset, TestLocator locator) { - if (locator != null) { - if (locator.appliesTo(fo)) { - if (locator.asynchronous()) { - locator.findOpposite(fo, caretOffset, new LocationListener() { - - @Override - public void foundLocation(FileObject fo, LocationResult location) { - if (location != null) { - FileObject fileObject = location.getFileObject(); - if(fileObject == null) { - String msg = location.getErrorMessage(); - if (msg != null) { - DialogDisplayer.getDefault().notify( - new NotifyDescriptor.Message(msg, NotifyDescriptor.INFORMATION_MESSAGE)); - } - } else { - locationResults.put(location, fileObject.getName()); - } - } - lock.release(); - } - }); - } else { - LocationResult opposite = locator.findOpposite(fo, caretOffset); - - if (opposite != null) { - FileObject fileObject = opposite.getFileObject(); - if (fileObject == null) { - String msg = opposite.getErrorMessage(); - if (msg != null) { - DialogDisplayer.getDefault().notify( - new NotifyDescriptor.Message(msg, NotifyDescriptor.INFORMATION_MESSAGE)); - } - } else { - locationResults.put(opposite, fileObject.getName()); - } - } - lock.release(); - } - } - } - } - @NbBundle.Messages("LBL_PickExpression=Go to Test") - private void showPopup(FileObject fo) { + private void showPopup(FileObject fo, Collection locations) { JTextComponent pane; Point l = new Point(-1, -1); @@ -243,51 +179,20 @@ private void showPopup(FileObject fo) { SwingUtilities.convertPointToScreen(l, pane); String label = Bundle.LBL_PickExpression(); - PopupUtil.showPopup(new OppositeCandidateChooser(this, label, locationResults), label, l.x, l.y, true, -1); + PopupUtil.showPopup(new OppositeCandidateChooser(this, label, locations), label, l.x, l.y, true, -1); } } catch (BadLocationException ex) { Logger.getLogger(GotoOppositeAction.class.getName()).log(Level.WARNING, null, ex); } } - public void handleResult(LocationResult opposite) { + public void handleResult(Location opposite) { FileObject fileObject = opposite.getFileObject(); if (fileObject != null) { NbDocument.openDocument(fileObject, opposite.getOffset(), Line.ShowOpenType.OPEN, Line.ShowVisibilityType.FOCUS); - } else if (opposite.getErrorMessage() != null) { - String msg = opposite.getErrorMessage(); - NotifyDescriptor descr = new NotifyDescriptor.Message(msg, - NotifyDescriptor.INFORMATION_MESSAGE); - DialogDisplayer.getDefault().notify(descr); - } - } - - private TestLocator getLocatorFor(FileObject fo) { - Collection locators = Lookup.getDefault().lookupAll(TestLocator.class); - for (TestLocator locator : locators) { - if (locator.appliesTo(fo)) { - return locator; - } } - - return null; } - private FileType getFileType(FileObject fo) { - TestLocator locator = getLocatorFor(fo); - if (locator != null) { - return locator.getFileType(fo); - } - - return FileType.NEITHER; - } - - private FileType getCurrentFileType() { - FileObject fo = getApplicableFileObject(new int[1]); - - return (fo != null) ? getFileType(fo) : FileType.NEITHER; - } - private FileObject getApplicableFileObject(int[] caretPosHolder) { if (!EventQueue.isDispatchThread()) { // Unsafe to ask for an editor pane from a random thread. diff --git a/ide/gototest/src/org/netbeans/modules/gototest/OppositeCandidateChooser.java b/ide/gototest/src/org/netbeans/modules/gototest/OppositeCandidateChooser.java index 8e1ec5f413c2..d1630e55eb86 100644 --- a/ide/gototest/src/org/netbeans/modules/gototest/OppositeCandidateChooser.java +++ b/ide/gototest/src/org/netbeans/modules/gototest/OppositeCandidateChooser.java @@ -24,12 +24,13 @@ import java.awt.event.FocusListener; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; -import java.util.HashMap; +import java.util.Collection; import javax.swing.DefaultListCellRenderer; import javax.swing.DefaultListModel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.ListModel; +import org.netbeans.api.gototest.TestOppositesLocator.Location; import org.netbeans.spi.gototest.TestLocator.LocationResult; /** @@ -39,10 +40,10 @@ public class OppositeCandidateChooser extends JPanel implements FocusListener { private final String caption; - private static HashMap toShow; + private static Collection toShow; private static GotoOppositeAction action; - public OppositeCandidateChooser(GotoOppositeAction action, String caption, HashMap toShow) { + public OppositeCandidateChooser(GotoOppositeAction action, String caption, Collection toShow) { this.caption = caption; OppositeCandidateChooser.action = action; OppositeCandidateChooser.toShow = toShow; @@ -122,14 +123,14 @@ private void jList1KeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_jL // End of variables declaration//GEN-END:variables private void openSelected() { - LocationResult locator = (LocationResult) jList1.getSelectedValue(); + Location locator = (Location) jList1.getSelectedValue(); action.handleResult(locator); PopupUtil.hidePopup(); } private ListModel createListModel() { DefaultListModel dlm = new DefaultListModel(); - for (LocationResult cand: toShow.keySet()) { + for (Location cand: toShow) { dlm.addElement(cand); } @@ -146,9 +147,9 @@ public Component getListCellRendererComponent( boolean cellHasFocus) { Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); - if (value instanceof LocationResult) { - LocationResult locator = (LocationResult) value; - setText(toShow.get(locator)); + if (value instanceof Location) { + Location location = (Location) value; + setText(location.getDisplayName()); } return c; diff --git a/java/java.lsp.server/nbproject/project.xml b/java/java.lsp.server/nbproject/project.xml index 4ec16db9ddc5..2ad0cde82e2e 100644 --- a/java/java.lsp.server/nbproject/project.xml +++ b/java/java.lsp.server/nbproject/project.xml @@ -305,6 +305,15 @@ 1.20 + + org.netbeans.modules.gototest + + + + 1 + 1.57 + + org.netbeans.modules.gsf.testrunner diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/commands/TestOppositesCommandProvider.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/commands/TestOppositesCommandProvider.java new file mode 100644 index 000000000000..11c4755c213c --- /dev/null +++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/commands/TestOppositesCommandProvider.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.java.lsp.server.commands; + +import com.google.gson.JsonPrimitive; +import java.net.MalformedURLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import org.netbeans.api.gototest.TestOppositesLocator; +import org.netbeans.modules.java.lsp.server.Utils; +import org.netbeans.spi.lsp.CommandProvider; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; +import org.openide.filesystems.FileObject; +import org.openide.util.lookup.ServiceProvider; + +@ServiceProvider(service=CommandProvider.class) +public class TestOppositesCommandProvider implements CommandProvider { + + private static final String NBLS_GO_TO_TEST = "nbls.go.to.test"; + private static final Set COMMANDS = new HashSet<>(Arrays.asList(NBLS_GO_TO_TEST)); + + @Override + public Set getCommands() { + return COMMANDS; + } + + @Override + public CompletableFuture runCommand(String command, List arguments) { + switch (command) { + case NBLS_GO_TO_TEST: { + try { + String source = ((JsonPrimitive) arguments.get(0)).getAsString(); + FileObject file; + file = Utils.fromUri(source); + return TestOppositesLocator.getDefault().findOpposites(file, -1).thenApply(locations -> locations); + } catch (MalformedURLException ex) { + return CompletableFuture.completedFuture(Collections.emptyList()); + } + } + default: + throw new UnsupportedOperationException("Command not supported: " + command); + } + } + +} diff --git a/java/java.lsp.server/vscode/package.json b/java/java.lsp.server/vscode/package.json index 78595718c8ef..07c3269640be 100644 --- a/java/java.lsp.server/vscode/package.json +++ b/java/java.lsp.server/vscode/package.json @@ -635,6 +635,11 @@ "command": "testing.runAll", "title": "Run All Tests", "category": "Test" + }, + { + "command": "nbls.open.test", + "title": "Go To Test/Tested class...", + "category": "Java" } ], "keybindings": [ @@ -661,6 +666,11 @@ { "command": "nbls.java.goto.super.implementation", "when": "nbJavaLSReady && editorLangId == java && editorTextFocus && config.netbeans.javaSupport.enabled", + "group": "navigation@99" + }, + { + "command": "nbls.open.test", + "when": "nbJavaLSReady && editorLangId == java", "group": "navigation@100" } ], @@ -669,6 +679,11 @@ "command": "nbls.workspace.new", "when": "nbJavaLSReady && explorerResourceIsFolder", "group": "navigation@3" + }, + { + "command": "nbls.open.test", + "when": "nbJavaLSReady && resourceExtname == .java", + "group": "goto@1" } ], "commandPalette": [ @@ -684,6 +699,10 @@ "command": "nbls.workspace.compile", "when": "nbJavaLSReady && config.netbeans.javaSupport.enabled" }, + { + "command": "nbls.open.test", + "when": "nbJavaLSReady && editorLangId == java" + }, { "command": "nbls.java.goto.super.implementation", "when": "nbJavaLSReady && editorLangId == java && config.netbeans.javaSupport.enabled" diff --git a/java/java.lsp.server/vscode/src/extension.ts b/java/java.lsp.server/vscode/src/extension.ts index 913484608c42..e0cf43fdd386 100644 --- a/java/java.lsp.server/vscode/src/extension.ts +++ b/java/java.lsp.server/vscode/src/extension.ts @@ -494,6 +494,60 @@ export function activate(context: ExtensionContext): VSNetBeansAPI { throw `Client ${c} doesn't support new project`; } })); + context.subscriptions.push(commands.registerCommand(COMMAND_PREFIX + '.open.test', async (ctx) => { + let c: LanguageClient = await client; + const commands = await vscode.commands.getCommands(); + if (commands.includes(COMMAND_PREFIX + '.go.to.test')) { + try { + const res: any = await vscode.commands.executeCommand(COMMAND_PREFIX + '.go.to.test', contextUri(ctx)?.toString()); + if("errorMessage" in res){ + throw new Error(res.errorMessage); + } + res?.providerErrors?.map((error: any) => { + if(error?.message){ + vscode.window.showErrorMessage(error.message); + } + }); + if (res?.locations?.length) { + if (res.locations.length === 1) { + const { file, offset } = res.locations[0]; + const filePath = vscode.Uri.parse(file); + const editor = await vscode.window.showTextDocument(filePath, { preview: false }); + if (offset != -1) { + const pos: vscode.Position = editor.document.positionAt(offset); + editor.selections = [new vscode.Selection(pos, pos)]; + const range = new vscode.Range(pos, pos); + editor.revealRange(range); + } + + } else { + const namePathMapping: { [key: string]: string } = {} + res.locations.forEach((fp:any) => { + const fileName = path.basename(fp.file); + namePathMapping[fileName] = fp.file + }); + const selected = await window.showQuickPick(Object.keys(namePathMapping), { + title: 'Select files to open', + placeHolder: 'Test files or source files associated to each other', + canPickMany: true + }); + if (selected) { + for await (const filePath of selected) { + let file = vscode.Uri.parse(filePath); + await vscode.window.showTextDocument(file, { preview: false }); + } + } else { + vscode.window.showInformationMessage("No file selected"); + } + } + } + } catch (err:any) { + vscode.window.showInformationMessage(err?.message || "No Test or Tested class found"); + } + } else { + throw `Client ${c} doesn't support go to test`; + } + })); context.subscriptions.push(commands.registerCommand(COMMAND_PREFIX + '.workspace.compile', () => wrapCommandWithProgress(COMMAND_PREFIX + '.build.workspace', 'Compiling workspace...', log, true) ));