IDEA-207961 Copy TBX reference URL: support multiple paths, testes provided

This commit is contained in:
Dmitry.Krasilschikov
2019-02-28 20:38:15 +02:00
parent f0be2f3ed7
commit c4d06c8ec1
20 changed files with 652 additions and 279 deletions

View File

@@ -15,7 +15,7 @@ import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class JavaVirtualFileQualifiedNameProvider implements CopyReferenceAction.VirtualFileQualifiedNameProvider {
public class JavaVirtualFileQualifiedNameProvider implements VirtualFileQualifiedNameProvider {
@Nullable
@Override
public String getQualifiedName(@NotNull Project project, @NotNull VirtualFile virtualFile) {

View File

@@ -0,0 +1,3 @@
class X<caret> {
String s = "x/x.txt";
}

View File

@@ -0,0 +1 @@
text

View File

@@ -0,0 +1 @@
text

View File

@@ -0,0 +1,4 @@
class C2 {
public <caret>C2(int i) {
}
}

View File

@@ -0,0 +1,14 @@
class C2 {
<selection><caret>public C2(int i) {
}
</selection>
<selection><caret>public C2(int i) {
}
</selection>
<selection><caret>public C2(int i) {
}
</selection>
<selection><caret>public C2(int i) {
}
</selection>
}

View File

@@ -0,0 +1,8 @@
class XXX {
void f() {
new C2();
new <caret>
}
}
class C2 {
}

View File

@@ -0,0 +1,27 @@
module DidYouMean
module Correctabl<caret>e
def original_message
method(:to_s).super_method.call
end
def to_s
msg = super.dup
if !cause.respond_to?(:corrections) || cause.corrections.empty?
msg << DidYouMean.formatter.message_for(corrections)
end
msg
rescue
super
end
def corrections
spell_checker.corrections
end
def spell_checker
@spell_checker ||= SPELL_CHECKERS[self.class.to_s].new(self)
end
end
end

View File

@@ -0,0 +1,8 @@
class X<caret> {
String s = "";
<selection>
String s = "";
String s = "";
String s = "";
</selection>
}

View File

@@ -0,0 +1,66 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.java.codeInsight
import com.intellij.JavaTestUtil
import com.intellij.ide.DataManager
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.LangDataKeys
import com.intellij.openapi.actionSystem.impl.SimpleDataContext
import com.intellij.openapi.ide.CopyPasteManager
import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase
import junit.framework.TestCase
import java.awt.datatransfer.DataFlavor
class CreateTBXReferenceTest : LightPlatformCodeInsightFixtureTestCase() {
override fun getTestDataPath(): String {
return "${JavaTestUtil.getJavaTestDataPath()}/codeInsight/copyTBXReference/"
}
fun testMethodReference() {
doTest("jetbrains://idea/navigate/reference?project=light_temp&fqn=C2#C2")
}
fun testSelection() {
doTest("jetbrains://idea/navigate/reference?project=light_temp&fqn=X&selection=3:3-7:3")
}
fun testMultipleSelections() {
doTest(
"jetbrains://idea/navigate/reference?project=light_temp&path=MultipleSelections.java:11:5&selection1=2:5-4:5&selection2=5:5-7:5&selection3=8:5-10:5&selection4=11:5-13:5")
}
fun testRubyClassReference() {
doTest("jetbrains://idea/navigate/reference?project=light_temp&path=RubyClassReference.rb:2:20",
getTestName(false) + ".rb")
}
fun testPathWithLocation() {
doTest("jetbrains://idea/navigate/reference?project=light_temp&path=PathWithLocation.java:4:13")
}
fun testMultipleFiles() {
val file1 = myFixture.configureByFile("File1.java")
val file2 = myFixture.configureByFile("File2.java")
doTest("jetbrains://idea/navigate/reference?project=light_temp&path1=File1.java&path2=File2.java",
fileName = null,
additionalDataContext = mapOf(LangDataKeys.PSI_ELEMENT_ARRAY.name to arrayOf(file1, file2)))
}
private fun doTest(expectedURL: String,
fileName: String? = getTestName(false) + ".java",
additionalDataContext: Map<String, Any> = hashMapOf()) {
fileName?.let { myFixture.configureByFile(fileName) }
var dataContext = DataManager.getInstance().getDataContext(myFixture.editor.component)
dataContext = SimpleDataContext.getSimpleContext(additionalDataContext, dataContext)
val action = ActionManager.getInstance().getAction("CopyTBXReference")
action.actionPerformed(
AnActionEvent(null, dataContext, ActionPlaces.MAIN_MENU, action.templatePresentation, ActionManager.getInstance(), 0))
TestCase.assertEquals(expectedURL, CopyPasteManager.getInstance().contents?.getTransferData(DataFlavor.stringFlavor))
}
}

View File

@@ -1,56 +1,37 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ide.actions;
import com.intellij.codeInsight.TargetElementUtil;
import com.intellij.codeInsight.daemon.impl.IdentifierUtil;
import com.intellij.codeInsight.highlighting.HighlightManager;
import com.intellij.ide.IdeBundle;
import com.intellij.ide.dnd.FileCopyPasteUtil;
import com.intellij.ide.scratch.RootType;
import com.intellij.ide.scratch.ScratchFileService;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.ide.CopyPasteManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.openapi.wm.ex.StatusBarEx;
import com.intellij.psi.*;
import com.intellij.util.ArrayUtilRt;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiFileSystemItem;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static com.intellij.ide.actions.CopyReferenceUtil.*;
/**
* @author Alexey
*/
public class CopyReferenceAction extends DumbAwareAction {
public static final DataFlavor ourFlavor = FileCopyPasteUtil.createJvmDataFlavor(MyTransferable.class);
public static final DataFlavor ourFlavor = FileCopyPasteUtil.createJvmDataFlavor(CopyReferenceFQNTransferable.class);
public CopyReferenceAction() {
super();
@@ -95,7 +76,11 @@ public class CopyReferenceAction extends DumbAwareAction {
Project project = CommonDataKeys.PROJECT.getData(dataContext);
List<PsiElement> elements = getElementsToCopy(editor, dataContext);
if (!doCopy(elements, project, editor) && editor != null && project != null) {
String copy = CopyReferenceUtil.doCopy(elements, editor);
if (copy != null) {
CopyPasteManager.getInstance().setContents(new CopyReferenceFQNTransferable(copy));
setStatusBarText(project, IdeBundle.message("message.reference.to.fqn.has.been.copied", copy));
} else if (editor != null && project != null) {
Document document = editor.getDocument();
PsiFile file = PsiDocumentManager.getInstance(project).getCachedPsiFile(document);
if (file != null) {
@@ -106,216 +91,23 @@ public class CopyReferenceAction extends DumbAwareAction {
return;
}
HighlightManager highlightManager = HighlightManager.getInstance(project);
EditorColorsManager manager = EditorColorsManager.getInstance();
TextAttributes attributes = manager.getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES);
if (elements.size() == 1 && editor != null && project != null) {
PsiElement element = elements.get(0);
PsiElement nameIdentifier = IdentifierUtil.getNameIdentifier(element);
if (nameIdentifier != null) {
highlightManager.addOccurrenceHighlights(editor, new PsiElement[]{nameIdentifier}, attributes, true, null);
} else {
PsiReference reference = TargetElementUtil.findReference(editor, editor.getCaretModel().getOffset());
if (reference != null) {
highlightManager.addOccurrenceHighlights(editor, new PsiReference[]{reference}, attributes, true, null);
} else if (element != PsiDocumentManager.getInstance(project).getCachedPsiFile(editor.getDocument())) {
highlightManager.addOccurrenceHighlights(editor, new PsiElement[]{element}, attributes, true, null);
}
}
}
}
@NotNull
private static List<PsiElement> getElementsToCopy(@Nullable final Editor editor, final DataContext dataContext) {
List<PsiElement> elements = ContainerUtil.newArrayList();
if (editor != null) {
PsiReference reference = TargetElementUtil.findReference(editor);
if (reference != null) {
ContainerUtil.addIfNotNull(elements, reference.getElement());
}
}
if (elements.isEmpty()) {
PsiElement[] psiElements = LangDataKeys.PSI_ELEMENT_ARRAY.getData(dataContext);
if (psiElements != null) {
Collections.addAll(elements, psiElements);
}
}
if (elements.isEmpty()) {
ContainerUtil.addIfNotNull(elements, CommonDataKeys.PSI_ELEMENT.getData(dataContext));
}
if (elements.isEmpty() && editor == null) {
final Project project = CommonDataKeys.PROJECT.getData(dataContext);
VirtualFile[] files = CommonDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext);
if (project != null && files != null) {
for (VirtualFile file : files) {
ContainerUtil.addIfNotNull(elements, PsiManager.getInstance(project).findFile(file));
}
}
}
return ContainerUtil.mapNotNull(elements, element -> element instanceof PsiFile && !((PsiFile)element).getViewProvider().isPhysical() ? null : adjustElement(element));
}
private static PsiElement adjustElement(PsiElement element) {
PsiElement adjustedElement = QualifiedNameProviderUtil.adjustElementToCopy(element);
return adjustedElement != null ? adjustedElement : element;
highlight(editor, project, elements);
}
public static boolean doCopy(final PsiElement element, final Project project) {
return doCopy(Arrays.asList(element), project, null);
return doCopy(Arrays.asList(element), project);
}
private static boolean doCopy(List<? extends PsiElement> elements, @Nullable final Project project, @Nullable Editor editor) {
if (elements.isEmpty()) return false;
List<String> fqns = ContainerUtil.newArrayList();
for (PsiElement element : elements) {
String fqn = elementToFqn(element, editor);
if (fqn == null) return false;
fqns.add(fqn);
}
String toCopy = StringUtil.join(fqns, "\n");
CopyPasteManager.getInstance().setContents(new MyTransferable(toCopy));
private static boolean doCopy(List<? extends PsiElement> elements, @Nullable final Project project) {
String toCopy = CopyReferenceUtil.doCopy(elements, null);
CopyPasteManager.getInstance().setContents(new CopyReferenceFQNTransferable(toCopy));
setStatusBarText(project, IdeBundle.message("message.reference.to.fqn.has.been.copied", toCopy));
return true;
}
private static void setStatusBarText(Project project, String message) {
if (project != null) {
final StatusBarEx statusBar = (StatusBarEx)WindowManager.getInstance().getStatusBar(project);
if (statusBar != null) {
statusBar.setInfo(message);
}
}
}
private static class MyTransferable implements Transferable {
private final String fqn;
MyTransferable(String fqn) {
this.fqn = fqn;
}
@Override
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[]{ourFlavor, DataFlavor.stringFlavor};
}
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
return ArrayUtilRt.find(getTransferDataFlavors(), flavor) != -1;
}
@Override
@Nullable
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
if (isDataFlavorSupported(flavor)) {
return fqn;
}
return null;
}
}
@Nullable
public static String elementToFqn(@Nullable final PsiElement element) {
return elementToFqn(element, null);
}
@Nullable
private static String elementToFqn(@Nullable final PsiElement element, @Nullable Editor editor) {
String result = getQualifiedNameFromProviders(element);
if (result != null) return result;
if (editor != null) { //IDEA-70346
PsiReference reference = TargetElementUtil.findReference(editor, editor.getCaretModel().getOffset());
if (reference != null) {
result = getQualifiedNameFromProviders(reference.resolve());
if (result != null) return result;
}
}
if (element instanceof PsiFile) {
return FileUtil.toSystemIndependentName(getFileFqn((PsiFile)element));
}
if (element instanceof PsiDirectory) {
return FileUtil.toSystemIndependentName(getVirtualFileFqn(((PsiDirectory)element).getVirtualFile(), element.getProject()));
}
return null;
}
@Nullable
private static String getQualifiedNameFromProviders(@Nullable PsiElement element) {
if (element == null) return null;
DumbService.getInstance(element.getProject()).setAlternativeResolveEnabled(true);
try {
return QualifiedNameProviderUtil.getQualifiedName(element);
}
finally {
DumbService.getInstance(element.getProject()).setAlternativeResolveEnabled(false);
}
}
@NotNull
private static String getFileFqn(final PsiFile file) {
final VirtualFile virtualFile = file.getVirtualFile();
return virtualFile == null ? file.getName() : getVirtualFileFqn(virtualFile, file.getProject());
}
@NotNull
private static String getVirtualFileFqn(@NotNull VirtualFile virtualFile, @NotNull Project project) {
for (VirtualFileQualifiedNameProvider provider : VirtualFileQualifiedNameProvider.EP_NAME.getExtensionList()) {
String qualifiedName = provider.getQualifiedName(project, virtualFile);
if (qualifiedName != null) {
return qualifiedName;
}
}
Module module = ProjectFileIndex.getInstance(project).getModuleForFile(virtualFile, false);
if (module != null) {
for (VirtualFile root : ModuleRootManager.getInstance(module).getContentRoots()) {
String relativePath = VfsUtilCore.getRelativePath(virtualFile, root);
if (relativePath != null) {
return relativePath;
}
}
}
String relativePath = VfsUtilCore.getRelativePath(virtualFile, project.getBaseDir());
if (relativePath != null) {
return relativePath;
}
RootType rootType = RootType.forFile(virtualFile);
if (rootType != null) {
VirtualFile scratchRootVirtualFile =
VfsUtil.findFileByIoFile(new File(ScratchFileService.getInstance().getRootPath(rootType)), false);
if (scratchRootVirtualFile != null) {
String scratchRelativePath = VfsUtilCore.getRelativePath(virtualFile, scratchRootVirtualFile);
if (scratchRelativePath != null) {
return scratchRelativePath;
}
}
}
return virtualFile.getPath();
}
public interface VirtualFileQualifiedNameProvider {
ExtensionPointName<VirtualFileQualifiedNameProvider> EP_NAME =
ExtensionPointName.create("com.intellij.virtualFileQualifiedNameProvider");
/**
* @return {@code virtualFile} fqn (relative path for example) or null if not handled by this provider
*/
@Nullable
String getQualifiedName(@NotNull Project project, @NotNull VirtualFile virtualFile);
return CopyReferenceUtil.elementToFqn(element, null);
}
}

View File

@@ -0,0 +1,37 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ide.actions;
import com.intellij.util.ArrayUtilRt;
import org.jetbrains.annotations.Nullable;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
class CopyReferenceFQNTransferable implements Transferable {
private final String fqn;
CopyReferenceFQNTransferable(String fqn) {
this.fqn = fqn;
}
@Override
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[]{CopyReferenceAction.ourFlavor, DataFlavor.stringFlavor};
}
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
return ArrayUtilRt.find(getTransferDataFlavors(), flavor) != -1;
}
@Override
@Nullable
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
if (isDataFlavorSupported(flavor)) {
return fqn;
}
return null;
}
}

View File

@@ -0,0 +1,203 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ide.actions;
import com.intellij.codeInsight.TargetElementUtil;
import com.intellij.codeInsight.daemon.impl.IdentifierUtil;
import com.intellij.codeInsight.highlighting.HighlightManager;
import com.intellij.ide.scratch.RootType;
import com.intellij.ide.scratch.ScratchFileService;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.LangDataKeys;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.openapi.wm.ex.StatusBarEx;
import com.intellij.psi.*;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.Collections;
import java.util.List;
public class CopyReferenceUtil {
static void highlight(Editor editor, Project project, List<PsiElement> elements) {
HighlightManager highlightManager = HighlightManager.getInstance(project);
EditorColorsManager manager = EditorColorsManager.getInstance();
TextAttributes attributes = manager.getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES);
if (elements.size() == 1 && editor != null && project != null) {
PsiElement element = elements.get(0);
PsiElement nameIdentifier = IdentifierUtil.getNameIdentifier(element);
if (nameIdentifier != null) {
highlightManager.addOccurrenceHighlights(editor, new PsiElement[]{nameIdentifier}, attributes, true, null);
}
else {
PsiReference reference = TargetElementUtil.findReference(editor, editor.getCaretModel().getOffset());
if (reference != null) {
highlightManager.addOccurrenceHighlights(editor, new PsiReference[]{reference}, attributes, true, null);
}
else if (element != PsiDocumentManager.getInstance(project).getCachedPsiFile(editor.getDocument())) {
highlightManager.addOccurrenceHighlights(editor, new PsiElement[]{element}, attributes, true, null);
}
}
}
}
@NotNull
static List<PsiElement> getElementsToCopy(@Nullable final Editor editor, final DataContext dataContext) {
List<PsiElement> elements = ContainerUtil.newArrayList();
if (editor != null) {
PsiReference reference = TargetElementUtil.findReference(editor);
if (reference != null) {
ContainerUtil.addIfNotNull(elements, reference.getElement());
}
}
if (elements.isEmpty()) {
PsiElement[] psiElements = LangDataKeys.PSI_ELEMENT_ARRAY.getData(dataContext);
if (psiElements != null) {
Collections.addAll(elements, psiElements);
}
}
if (elements.isEmpty()) {
ContainerUtil.addIfNotNull(elements, CommonDataKeys.PSI_ELEMENT.getData(dataContext));
}
if (elements.isEmpty() && editor == null) {
final Project project = CommonDataKeys.PROJECT.getData(dataContext);
VirtualFile[] files = CommonDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext);
if (project != null && files != null) {
for (VirtualFile file : files) {
ContainerUtil.addIfNotNull(elements, PsiManager.getInstance(project).findFile(file));
}
}
}
return ContainerUtil.mapNotNull(elements, element -> element instanceof PsiFile && !((PsiFile)element).getViewProvider().isPhysical()
? null
: adjustElement(element));
}
static PsiElement adjustElement(PsiElement element) {
PsiElement adjustedElement = QualifiedNameProviderUtil.adjustElementToCopy(element);
return adjustedElement != null ? adjustedElement : element;
}
static void setStatusBarText(Project project, String message) {
if (project != null) {
final StatusBarEx statusBar = (StatusBarEx)WindowManager.getInstance().getStatusBar(project);
if (statusBar != null) {
statusBar.setInfo(message);
}
}
}
@Nullable
static String getQualifiedNameFromProviders(@Nullable PsiElement element) {
if (element == null) return null;
DumbService.getInstance(element.getProject()).setAlternativeResolveEnabled(true);
try {
return QualifiedNameProviderUtil.getQualifiedName(element);
}
finally {
DumbService.getInstance(element.getProject()).setAlternativeResolveEnabled(false);
}
}
static String doCopy(List<? extends PsiElement> elements, @Nullable Editor editor) {
if (elements.isEmpty()) return null;
List<String> fqns = ContainerUtil.newArrayList();
for (PsiElement element : elements) {
String fqn = elementToFqn(element, editor);
if (fqn == null) return null;
fqns.add(fqn);
}
return StringUtil.join(fqns, "\n");
}
@Nullable
static String elementToFqn(@Nullable final PsiElement element, @Nullable Editor editor) {
String result = getQualifiedNameFromProviders(element);
if (result != null) return result;
if (editor != null) { //IDEA-70346
PsiReference reference = TargetElementUtil.findReference(editor, editor.getCaretModel().getOffset());
if (reference != null) {
result = getQualifiedNameFromProviders(reference.resolve());
if (result != null) return result;
}
}
if (element instanceof PsiFile) {
return FileUtil.toSystemIndependentName(getFileFqn((PsiFile)element));
}
if (element instanceof PsiDirectory) {
return FileUtil.toSystemIndependentName(getVirtualFileFqn(((PsiDirectory)element).getVirtualFile(), element.getProject()));
}
return null;
}
@NotNull
static String getFileFqn(final PsiFile file) {
final VirtualFile virtualFile = file.getVirtualFile();
return virtualFile == null ? file.getName() : getVirtualFileFqn(virtualFile, file.getProject());
}
@NotNull
private static String getVirtualFileFqn(@NotNull VirtualFile virtualFile, @NotNull Project project) {
for (VirtualFileQualifiedNameProvider provider : VirtualFileQualifiedNameProvider.EP_NAME.getExtensionList()) {
String qualifiedName = provider.getQualifiedName(project, virtualFile);
if (qualifiedName != null) {
return qualifiedName;
}
}
Module module = ProjectFileIndex.getInstance(project).getModuleForFile(virtualFile, false);
if (module != null) {
for (VirtualFile root : ModuleRootManager.getInstance(module).getContentRoots()) {
String relativePath = VfsUtilCore.getRelativePath(virtualFile, root);
if (relativePath != null) {
return relativePath;
}
}
}
String relativePath = VfsUtilCore.getRelativePath(virtualFile, project.getBaseDir());
if (relativePath != null) {
return relativePath;
}
RootType rootType = RootType.forFile(virtualFile);
if (rootType != null) {
VirtualFile scratchRootVirtualFile =
VfsUtil.findFileByIoFile(new File(ScratchFileService.getInstance().getRootPath(rootType)), false);
if (scratchRootVirtualFile != null) {
String scratchRelativePath = VfsUtilCore.getRelativePath(virtualFile, scratchRootVirtualFile);
if (scratchRelativePath != null) {
return scratchRelativePath;
}
}
}
return virtualFile.getPath();
}
}

View File

@@ -0,0 +1,183 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ide.actions
import com.intellij.ide.IdeBundle
import com.intellij.ide.actions.CopyReferenceUtil.*
import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.JetBrainsProtocolHandler
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.ide.CopyPasteManager
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.impl.JBProtocolNavigateCommand.Companion.FQN_KEY
import com.intellij.openapi.project.impl.JBProtocolNavigateCommand.Companion.NAVIGATE_COMMAND
import com.intellij.openapi.project.impl.JBProtocolNavigateCommand.Companion.PATH_KEY
import com.intellij.openapi.project.impl.JBProtocolNavigateCommand.Companion.PROJECT_NAME_KEY
import com.intellij.openapi.project.impl.JBProtocolNavigateCommand.Companion.REFERENCE_TARGET
import com.intellij.openapi.project.impl.JBProtocolNavigateCommand.Companion.SELECTION
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFileSystemItem
import com.intellij.util.PlatformUtils
import com.intellij.util.PlatformUtils.*
import java.awt.datatransfer.StringSelection
import java.util.stream.Collectors
import java.util.stream.IntStream
class CopyTBXReferenceAction : DumbAwareAction() {
init {
isEnabledInModalContext = true
setInjectedContext(true)
}
override fun update(e: AnActionEvent) {
var plural = false
var enabled: Boolean
var paths = false
val dataContext = e.dataContext
val editor = CommonDataKeys.EDITOR.getData(dataContext)
if (editor != null && FileDocumentManager.getInstance().getFile(editor.document) != null) {
enabled = true
}
else {
val elements = getElementsToCopy(editor, dataContext)
enabled = !elements.isEmpty()
plural = elements.size > 1
paths = elements.stream().allMatch { el -> el is PsiFileSystemItem && getQualifiedNameFromProviders(el) == null }
}
enabled = enabled && (ActionPlaces.MAIN_MENU == e.place)
e.presentation.isEnabled = enabled
if (ActionPlaces.isPopupPlace(e.place)) {
e.presentation.isVisible = enabled
}
else {
e.presentation.isVisible = true
}
e.presentation.text = if (paths)
if (plural) "Cop&y Toolbox Relative Paths URL" else "Cop&y Toolbox Relative Path URL"
else if (plural) "Cop&y Toolbox References URL" else "Cop&y Toolbox Reference URL"
}
override fun actionPerformed(e: AnActionEvent) {
val dataContext = e.dataContext
val editor = CommonDataKeys.EDITOR.getData(dataContext)
val project = CommonDataKeys.PROJECT.getData(dataContext)
val elements = getElementsToCopy(editor, dataContext)
if (project == null) {
LOG.warn("'Copy TBX Reference' action cannot find project.")
return
}
var copy = createJetbrainsLink(project, elements, editor)
if (copy != null) {
CopyPasteManager.getInstance().setContents(CopyReferenceFQNTransferable(copy))
setStatusBarText(project, IdeBundle.message("message.reference.to.fqn.has.been.copied", copy))
}
else if (editor != null) {
val document = editor.document
val file = PsiDocumentManager.getInstance(project).getCachedPsiFile(document)
if (file != null) {
val logicalPosition = editor.caretModel.logicalPosition
val path = "${getFileFqn(file)}:${logicalPosition.line + 1}:${logicalPosition.column + 1}"
copy = createLink(editor, project, createRefs(true, path, ""))
CopyPasteManager.getInstance().setContents(StringSelection(copy))
setStatusBarText(project, "$copy has been copied")
}
return
}
highlight(editor, project, elements)
}
companion object {
private val LOG = Logger.getInstance(CopyTBXReferenceAction::class.java)
private const val JETBRAINS_NAVIGATE = JetBrainsProtocolHandler.PROTOCOL
private val IDE_TAGS = mapOf(IDEA_PREFIX to "idea",
IDEA_CE_PREFIX to "idea",
APPCODE_PREFIX to "appcode",
CLION_PREFIX to "clion",
PYCHARM_PREFIX to "pycharm",
PYCHARM_CE_PREFIX to "pycharm",
PYCHARM_EDU_PREFIX to "pycharm",
PHP_PREFIX to "php-storm",
RUBY_PREFIX to "rubymine",
WEB_PREFIX to "web-storm",
RIDER_PREFIX to "rd",
GOIDE_PREFIX to "goland")
fun createJetbrainsLink(project: Project, elements: List<PsiElement>, editor: Editor?): String? {
val refsParameters =
IntArray(elements.size) { i -> i }
.associateBy({ it }, { elementToFqn(elements[it], editor) })
.filter { it.value != null }
.entries
.ifEmpty { return null }
.joinToString("") { createRefs(isFile(elements[it.key]), it.value, parameterIndex(it.key, elements.size)) }
return createLink(editor, project, refsParameters)
}
private fun isFile(element: PsiElement) = element is PsiFileSystemItem && getQualifiedNameFromProviders(element) == null
private fun parameterIndex(index: Int, size: Int) = if (size == 1) "" else "${index + 1}"
private fun createRefs(isFile: Boolean, reference: String?, index: String) = "&${if (isFile) PATH_KEY else FQN_KEY}${index}=$reference"
private fun createLink(editor: Editor?, project: Project, refsParameters: String?): String? {
val tool = IDE_TAGS[PlatformUtils.getPlatformPrefix()]
if (tool == null) {
LOG.warn("Cannot find TBX tool for IDE: ${PlatformUtils.getPlatformPrefix()}")
return null
}
val selectionParameters = getSelectionParameters(editor) ?: ""
val projectParameter = "$PROJECT_NAME_KEY=${project.name}"
return "$JETBRAINS_NAVIGATE$tool/$NAVIGATE_COMMAND/$REFERENCE_TARGET?$projectParameter$refsParameters$selectionParameters"
}
private fun getSelectionParameters(editor: Editor?): String? {
if (editor == null) {
return null
}
ApplicationManager.getApplication().assertReadAccessAllowed()
if (editor.caretModel.supportsMultipleCarets()) {
val carets = editor.caretModel.allCarets
return IntStream.range(0, carets.size).mapToObj { i -> getSelectionParameters(editor, carets[i], parameterIndex(i, carets.size)) }
.filter { it != null }.collect(Collectors.joining())
}
else {
return getSelectionParameters(editor, editor.caretModel.currentCaret, "")
}
}
private fun getSelectionParameters(editor: Editor, caret: Caret, index: String): String? =
getSelectionRange(editor, caret)?.let { "&$SELECTION$index=$it" }
private fun getSelectionRange(editor: Editor, caret: Caret): String? {
if (!caret.hasSelection()) {
return null
}
val selectionStart = editor.visualToLogicalPosition(caret.selectionStartPosition)
val selectionEnd = editor.visualToLogicalPosition(caret.selectionEndPosition)
return String.format("%d:%d-%d:%d",
selectionStart.line + 1,
selectionStart.column + 1,
selectionEnd.line + 1,
selectionEnd.column + 1)
}
}
}

View File

@@ -0,0 +1,19 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ide.actions;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface VirtualFileQualifiedNameProvider {
ExtensionPointName<VirtualFileQualifiedNameProvider> EP_NAME =
ExtensionPointName.create("com.intellij.virtualFileQualifiedNameProvider");
/**
* @return {@code virtualFile} fqn (relative path for example) or null if not handled by this provider
*/
@Nullable
String getQualifiedName(@NotNull Project project, @NotNull VirtualFile virtualFile);
}

View File

@@ -8,6 +8,7 @@ import com.intellij.ide.util.gotoByName.*
import com.intellij.navigation.NavigationItem
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.JBProtocolCommand
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.progress.EmptyProgressIndicator
@@ -17,14 +18,13 @@ import com.intellij.openapi.project.ProjectManagerListener
import com.intellij.openapi.wm.IdeFocusManager
import com.intellij.util.messages.MessageBusConnection
class JBProtocolNavigateCommand : JBProtocolCommand("navigate") {
class JBProtocolNavigateCommand : JBProtocolCommand(NAVIGATE_COMMAND) {
override fun perform(target: String, parameters: Map<String, String>) {
// handles URLs of the following types:
// jetbrains://idea/navigate/reference?project=IDEA&reference=com.intellij.openapi.project.impl.JBProtocolNavigateCommand[.perform][#perform]
// [&selectionX=25:5-26:6]+
// jetbrains://idea/navigate/path?project=IDEA&path=com/intellij/openapi/project/impl/JBProtocolNavigateCommand.kt:23:1
// jetbrains://idea/navigate/reference?project=IDEA
// [&reference=com.intellij.openapi.project.impl.JBProtocolNavigateCommand[.perform][#perform]]+
// [&path=com/intellij/openapi/project/impl/JBProtocolNavigateCommand.kt:23:1]+
// [&selectionX=25:5-26:6]+
val projectName = parameters[PROJECT_NAME_KEY]
@@ -32,36 +32,22 @@ class JBProtocolNavigateCommand : JBProtocolCommand("navigate") {
return
}
val model: (Project) -> ChooseByNameModel
val pattern: String
when (target) {
REFERENCE_TARGET -> {
pattern = (parameters[FQN_KEY] ?: return)
model = { project -> GotoSymbolModel2(project) }
}
FILE_TARGET -> {
pattern = (parameters[PATH_KEY] ?: return)
model = { it -> GotoFileModel(it) }
}
else -> return
if (target != REFERENCE_TARGET) {
LOG.warn("JB navigate action supports only reference target, got $target")
return
}
val recentProjectsActions = RecentProjectsManager.getInstance().getRecentProjectsActions(false)
for (recentProjectAction in recentProjectsActions) {
if (recentProjectAction is ReopenProjectAction) {
if (recentProjectAction.projectName == projectName) {
val project = ProjectManager.getInstance().openProjects.find { project -> project.name == projectName }
val selections = parseSelections(parameters)
if (project != null) {
navigateToFile(project, model(project), pattern, selections)
}
else {
ProjectManager.getInstance().openProjects.find { project -> project.name == projectName }?.let {
findAndNavigateToReference(it, parameters)
} ?: run {
RecentProjectsManagerBase.getInstanceEx().doOpenProject(recentProjectAction.projectPath, null, false)
val appConnection = ApplicationManager.getApplication().messageBus.connect()
appConnection.subscribe(ProjectManager.TOPIC, NavigatableProjectListener(model, pattern, selections, appConnection))
appConnection.subscribe(ProjectManager.TOPIC, NavigatableProjectListener(appConnection, parameters))
}
}
}
@@ -69,21 +55,36 @@ class JBProtocolNavigateCommand : JBProtocolCommand("navigate") {
}
companion object {
private const val PROJECT_NAME_KEY = "project"
private const val SELECTION = "selection"
private val LOG = Logger.getInstance(JBProtocolNavigateCommand::class.java)
const val NAVIGATE_COMMAND = "navigate"
const val PROJECT_NAME_KEY = "project"
const val REFERENCE_TARGET = "reference"
const val PATH_KEY = "path"
const val FQN_KEY = "fqn"
const val SELECTION = "selection"
private const val REFERENCE_TARGET = "reference"
private const val FQN_KEY = "fqn"
private fun findAndNavigateToReference(project: Project, parameters: Map<String, String>) {
val selections = parseSelections(parameters)
navigateToReference(parameters, project, selections, FQN_KEY, GotoSymbolModel2(project))
navigateToReference(parameters, project, selections, PATH_KEY, GotoFileModel(project))
private const val FILE_TARGET = "file"
private const val PATH_KEY = "path"
}
private fun navigateToFile(project: Project,
gotoModel: ChooseByNameModel,
pattern: String,
selections: List<Pair<LogicalPosition, LogicalPosition>>) {
val model = JBProtocolNavigateChooseByNameViewModel(project, gotoModel)
DefaultChooseByNameItemProvider.filterElements(model, pattern, true, EmptyProgressIndicator(), null) {
private fun navigateToReference(parameters: Map<String, String>,
project: Project,
selections: List<Pair<LogicalPosition, LogicalPosition>>,
parameterKey: String,
model: ChooseByNameModel) {
parameters.filter { it.key.startsWith(parameterKey) }.forEach {
navigate(model, it.value, project, selections)
}
}
private fun navigate(model: ChooseByNameModel,
pattern: String,
project: Project,
selections: List<Pair<LogicalPosition, LogicalPosition>>) {
DefaultChooseByNameItemProvider.filterElements(MySearchModel(project, model), pattern, true, EmptyProgressIndicator(), null) {
if (it !is NavigationItem) {
return@filterElements true
}
@@ -131,19 +132,16 @@ class JBProtocolNavigateCommand : JBProtocolCommand("navigate") {
}
}
private class NavigatableProjectListener(private val model: (Project) -> ChooseByNameModel,
private val pattern: String,
private val selections: List<Pair<LogicalPosition, LogicalPosition>>,
private val appConnection: MessageBusConnection) : ProjectManagerListener {
private class NavigatableProjectListener(private val appConnection: MessageBusConnection,
private val parameters: Map<String, String>) : ProjectManagerListener {
override fun projectOpened(project: Project) {
navigateToFile(project, model(project), pattern, selections)
findAndNavigateToReference(project, parameters)
appConnection.disconnect()
}
}
//todo duplicates
private class JBProtocolNavigateChooseByNameViewModel(private val project: Project,
private val model: ChooseByNameModel) : ChooseByNameViewModel {
//todo duplicate
private class MySearchModel(private val project: Project, private val model: ChooseByNameModel) : ChooseByNameViewModel {
override fun canShowListForEmptyPattern() = false
override fun isSearchInAnyPlace(): Boolean = false

View File

@@ -354,6 +354,8 @@ action.CopyPaths.text=C_opy Paths
action.CopyPaths.description=Copy paths corresponding to selected files or directories to clipboard
action.CopyReference.text=Cop_y Reference
action.CopyReference.description=Copy reference to selected class, method or function, or a relative path to selected file
action.CopyTBXReference.text=Copy _Toolbox Reference URL
action.CopyTBXReference.description=Copy Toolbox reference URL to selected class, method or function, or a relative path to selected file
action.CopySettingsPath.text=Copy Settings Path
action.CopySettingsPath.mac.text=Copy Preferences Path
action.CopySettingsPath.description=Copy relative path to selected configurable option

View File

@@ -667,7 +667,7 @@
<extensionPoint name="qualifiedNameProvider" interface="com.intellij.ide.actions.QualifiedNameProvider"/>
<extensionPoint name="virtualFileQualifiedNameProvider"
interface="com.intellij.ide.actions.CopyReferenceAction$VirtualFileQualifiedNameProvider"/>
interface="com.intellij.ide.actions.VirtualFileQualifiedNameProvider"/>
<extensionPoint name="completionData"
beanClass="com.intellij.codeInsight.completion.CompletionDataEP"/>

View File

@@ -108,6 +108,10 @@
<add-to-group group-id="EditorPopupMenu" anchor="after" relative-to-action="$Copy"/>
</action>
<action id="CopyTBXReference" class="com.intellij.ide.actions.CopyTBXReferenceAction" >
<add-to-group group-id="CutCopyPasteGroup" anchor="after" relative-to-action="CopyReference"/>
</action>
<action id="CopyAsRichText" class="com.intellij.openapi.editor.richcopy.CopyAsRichTextAction">
<add-to-group group-id="CutCopyPasteGroup" anchor="after" relative-to-action="CopyPaths"/>
<add-to-group group-id="EditorPopupMenu" anchor="after" relative-to-action="$Copy"/>

View File

@@ -601,6 +601,9 @@
<action id="CopyReference">
<keyboard-shortcut first-keystroke="control alt shift C"/>
</action>
<action id="CopyTBXReference">
<keyboard-shortcut first-keystroke="control alt shift T"/>
</action>
<action id="EditorPasteFromX11">
<mouse-shortcut keystroke="button2"/>
</action>