mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
UX-600 copy reference popup - fix copy reference on new line, fix multiple files TBX ref
GitOrigin-RevId: 8f4feff34f7bc9b1038de28aa48d3d202e049a9b
This commit is contained in:
committed by
intellij-monorepo-bot
parent
e1b64208dc
commit
c6ad7d2a3b
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user