UX-600 copy reference popup - fix copy reference on new line, fix multiple files TBX ref

GitOrigin-RevId: 8f4feff34f7bc9b1038de28aa48d3d202e049a9b
This commit is contained in:
Dmitry.Krasilschikov
2019-08-27 00:03:48 +03:00
committed by intellij-monorepo-bot
parent e1b64208dc
commit c6ad7d2a3b
5 changed files with 92 additions and 196 deletions
@@ -3,7 +3,6 @@ package com.intellij.java.codeInsight
import com.intellij.JavaTestUtil
import com.intellij.ide.DataManager
import com.intellij.idea.Bombed
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.AnActionEvent
@@ -27,7 +26,6 @@ class CreateTBXReferenceTest : BasePlatformTestCase() {
doTest("jetbrains://idea/navigate/reference?project=light_temp&fqn=X&selection=3:3-7:3")
}
@Bombed(year = 2019, month = 9, day = 10, user = "kradima")
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")
@@ -38,7 +36,6 @@ class CreateTBXReferenceTest : BasePlatformTestCase() {
getTestName(false) + ".groovy")
}
@Bombed(year = 2019, month = 9, day = 10, user = "kradima")
fun testPathWithLocation() {
doTest("jetbrains://idea/navigate/reference?project=light_temp&path=PathWithLocation.java:4:13")
}
@@ -16,6 +16,7 @@ import com.intellij.openapi.roots.ProjectFileIndex
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiElement
import java.awt.datatransfer.StringSelection
abstract class CopyPathProvider : DumbAwareAction() {
override fun update(e: AnActionEvent) {
@@ -41,10 +42,13 @@ abstract class CopyPathProvider : DumbAwareAction() {
val dataContext = e.dataContext
val editor = CommonDataKeys.EDITOR.getData(dataContext)
val copy = project?.let { getQualifiedName(it, getElementsToCopy(editor, dataContext), editor) }
val elements = getElementsToCopy(editor, dataContext)
val copy = project?.let { getQualifiedName(it, elements, editor) }
CopyPasteManager.getInstance().setContents(CopyReferenceFQNTransferable(copy))
CopyPasteManager.getInstance().setContents(StringSelection(copy))
setStatusBarText(project, IdeBundle.message("message.path.to.fqn.has.been.copied", copy))
CopyReferenceUtil.highlight(editor, project, elements)
}
open fun getQualifiedName(project: Project?, elements: List<PsiElement>, editor: Editor?): String? {
@@ -112,6 +116,6 @@ class CopySourceRootPathProvider : CopyPathProvider() {
class CopyTBXReferenceProvider : CopyPathProvider() {
override fun getQualifiedName(project: Project?,
elements: List<PsiElement>,
editor: Editor?): String =
project?.let { CopyTBXReferenceAction.createJetbrainsLink(project, elements, editor) } ?: ""
editor: Editor?): String? =
project?.let { CopyTBXReferenceAction.createJetbrainsLink(project, elements, editor) }
}
@@ -1,17 +1,14 @@
// 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.ide.IdeBundle;
import com.intellij.ide.dnd.FileCopyPasteUtil;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.ide.CopyPasteManager;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -35,8 +32,7 @@ class CopyReferencePopupAction extends DumbAwareAction {
presentation.setEnabled(enabled);
presentation.setVisible(!ActionPlaces.isPopupPlace(e.getPlace()) || enabled);
presentation.setText(elements.size() > 1 ? "References" : "Reference");
presentation.putClientProperty(CopyReferencePopup.COPY_REFERENCE_KEY,
doElementsCopy(getElementsToCopy(editor, dataContext), editor));
presentation.putClientProperty(CopyReferencePopup.COPY_REFERENCE_KEY, doElementsCopy(getElementsToCopy(editor, dataContext), editor));
}
@Override
@@ -58,37 +54,9 @@ class CopyReferencePopupAction extends DumbAwareAction {
}
}
@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 doElementsCopy(@NotNull List<? extends PsiElement> elements, @Nullable Editor editor) {
if (elements.isEmpty()) return null;
return StreamEx.of(elements).map(element -> getQualifiedReference(element, editor)).filter(fqn -> fqn != null).joining("\n");
}
@Nullable
private static String getQualifiedReference(@Nullable 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;
}
}
return null;
return StreamEx.of(elements).map(element -> elementToFqn(element, editor)).filter(fqn -> fqn != null).joining("\n");
}
}
@@ -1,7 +1,6 @@
// 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.navigation.JBProtocolNavigateCommand.Companion.FQN_KEY
import com.intellij.navigation.JBProtocolNavigateCommand.Companion.NAVIGATE_COMMAND
@@ -9,185 +8,115 @@ import com.intellij.navigation.JBProtocolNavigateCommand.Companion.PATH_KEY
import com.intellij.navigation.JBProtocolNavigateCommand.Companion.PROJECT_NAME_KEY
import com.intellij.navigation.JBProtocolNavigateCommand.Companion.REFERENCE_TARGET
import com.intellij.navigation.JBProtocolNavigateCommand.Companion.SELECTION
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.util.io.FileUtil
import com.intellij.openapi.util.registry.Registry
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFileSystemItem
import com.intellij.util.PlatformUtils.*
import com.intellij.util.io.encodeUrlQueryParameter
import java.awt.datatransfer.StringSelection
import java.util.stream.Collectors
import java.util.stream.IntStream
class CopyTBXReferenceAction : DumbAwareAction() {
init {
isEnabledInModalContext = true
setInjectedContext(true)
}
object CopyTBXReferenceAction {
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")
override fun update(e: AnActionEvent) {
if (!Registry.`is`("copy.tbx.reference.enabled")) {
e.presentation.isEnabledAndVisible = false
return
fun createJetbrainsLink(project: Project, elements: List<PsiElement>, editor: Editor?): String? {
val entries = IntArray(elements.size) { i -> i }
.associateBy({ it }, { elementToFqn(elements[it], editor) })
.filter { it.value != null }
.mapValues { FileUtil.getLocationRelativeToUserHome(it.value, false) }
.entries
val refsParameters = if (entries.isEmpty()) null
else entries.joinToString("") {
createRefs(isFile(elements[it.key]), if (elements.size > 1) it.value.encodeUrlQueryParameter() else it.value,
parameterIndex(it.key, elements.size))
}
var plural = false
var enabled: Boolean
var paths = false
val copy = createLink(editor, project, refsParameters)
if (copy != null) return copy
val dataContext = e.dataContext
val editor = CommonDataKeys.EDITOR.getData(dataContext)
if (editor != null && FileDocumentManager.getInstance().getFile(editor.document) != null) {
enabled = true
if (editor == null) return null
val file = PsiDocumentManager.getInstance(project).getCachedPsiFile(editor.document)
if (file == null) return null
val logicalPosition = editor.caretModel.logicalPosition
val path = "${getFileFqn(file)}:${logicalPosition.line + 1}:${logicalPosition.column + 1}"
return createLink(editor, project, createRefs(true, path, ""))
}
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? {
if (refsParameters == null) return null
val tool = IDE_TAGS[getPlatformPrefix()]
if (tool == null) {
LOG.warn("Cannot find TBX tool for IDE: ${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 {
val elements = getElementsToCopy(editor, dataContext)
enabled = !elements.isEmpty()
plural = elements.size > 1
paths = elements.stream().allMatch { el -> el is PsiFileSystemItem && getQualifiedNameFromProviders(el) == null }
return getSelectionParameters(editor, editor.caretModel.currentCaret, "")
}
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)
private fun getSelectionParameters(editor: Editor, caret: Caret, index: String): String? =
getSelectionRange(editor, caret)?.let { "&$SELECTION$index=$it" }
if (project == null) {
LOG.warn("'Copy TBX Reference' action cannot find project.")
return
private fun getSelectionRange(editor: Editor, caret: Caret): String? {
if (!caret.hasSelection()) {
return null
}
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
}
val selectionStart = editor.visualToLogicalPosition(caret.selectionStartPosition)
val selectionEnd = editor.visualToLogicalPosition(caret.selectionEndPosition)
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 }
.mapValues { FileUtil.getLocationRelativeToUserHome(it.value, false) }
.entries
.ifEmpty { return null }
.joinToString("") {
val reference = if (elements.size > 1) it.value.encodeUrlQueryParameter() else it.value
createRefs(isFile(elements[it.key]), reference, 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[getPlatformPrefix()]
if (tool == null) {
LOG.warn("Cannot find TBX tool for IDE: ${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)
}
return String.format("%d:%d-%d:%d",
selectionStart.line + 1,
selectionStart.column + 1,
selectionEnd.line + 1,
selectionEnd.column + 1)
}
}
@@ -346,8 +346,6 @@ 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