Skip to content
This repository has been archived by the owner on Sep 17, 2023. It is now read-only.

Add navigation to twine labels from XML files #49

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ intellij {
runIde {

if (Os.isFamily(Os.FAMILY_MAC)) {
// // to run with the real Android Studio
// ideDirectory = '/Applications/Android Studio.app/Contents'
// to run with the real Android Studio
// ideDir.set(file("/Applications/Android Studio.app/Contents"))
}
}

Expand Down
1 change: 1 addition & 0 deletions src/main/kotlin/by/overpass/twap/Twine.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ object Twine {
const val EXT = "twine"
val icon = IconLoader.getIcon("/icons/ic_twine.png", Twine.javaClass)
val androidStringResourceRegex = "R\\.string\\.(.+)".toRegex()
val androidStringXmlReferenceRegex = "@string/(.+)".toRegex()
val twineIdentifierRegex = "[a-zA-Z0-9_]+(\\.[a-zA-Z0-9_]+)*".toRegex()
}
45 changes: 0 additions & 45 deletions src/main/kotlin/by/overpass/twap/lang/TwineLineMarkerProvider.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package by.overpass.twap.lang.navigation

import by.overpass.twap.Twine
import by.overpass.twap.lang.reference.identifier.underscoreReplacement
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiIdentifier
import com.intellij.psi.PsiReferenceExpression
import com.intellij.util.castSafelyTo

/**
* Provides line marker navigation to corresponding [by.overpass.twap.lang.parsing.psi.TwineIdentifier]s from Java
*/
class JavaTwineLabelLineMarkerProvider : TwineLabelLineMarkerProvider() {

override fun collectNavigationMarkers(
element: PsiElement,
result: NavMarkersResultCollection,
) {
element.takeIf { it is PsiIdentifier }
?.castSafelyTo<PsiIdentifier>()
?.takeIf { it.parent is PsiReferenceExpression }
?.parent
?.let { Twine.androidStringResourceRegex.find(it.text) }
?.groupValues
?.get(1)
?.run(underscoreReplacement)
?.let { collectTwineIdRefs(element, result, it) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Line Marker Provider for navigation to Twine labels
*/

package by.overpass.twap.lang.navigation

import by.overpass.twap.Twine
import by.overpass.twap.lang.findTwineIds
import com.intellij.codeInsight.daemon.RelatedItemLineMarkerInfo
import com.intellij.codeInsight.daemon.RelatedItemLineMarkerProvider
import com.intellij.codeInsight.navigation.NavigationGutterIconBuilder
import com.intellij.psi.PsiElement

typealias NavMarkersResultCollection = MutableCollection<in RelatedItemLineMarkerInfo<*>?>

/**
* Provides line marker navigation to corresponding [by.overpass.twap.lang.parsing.psi.TwineIdentifier]s
*/
open class TwineLabelLineMarkerProvider : RelatedItemLineMarkerProvider() {
/**
* @param element currently processed [PsiElement]
* @param result result nav markers collection
* @param id sought twine label id
*/
protected fun collectTwineIdRefs(
element: PsiElement,
result: NavMarkersResultCollection,
id: String,
) {
element.project
.findTwineIds(id)
.takeIf { it.isNotEmpty() }
?.let {
val builder = NavigationGutterIconBuilder.create(Twine.icon)
.setTargets(it)
.setTooltipText(MESSAGE_NAVIGATE_TO_TWINE_LABEL)
result += builder.createLineMarkerInfo(element)
}
}

companion object {
const val MESSAGE_NAVIGATE_TO_TWINE_LABEL = "Navigate to Twine label"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package by.overpass.twap.lang.navigation

import by.overpass.twap.Twine
import com.intellij.psi.PsiElement
import com.intellij.psi.xml.XmlToken
import com.intellij.psi.xml.XmlTokenType
import com.intellij.util.castSafelyTo

/**
* Provides line marker navigation to corresponding [by.overpass.twap.lang.parsing.psi.TwineIdentifier]s from XML
*/
class XmlTwineLabelLineMarkerProvider : TwineLabelLineMarkerProvider() {

override fun collectNavigationMarkers(
element: PsiElement,
result: NavMarkersResultCollection,
) {
element.takeIf { it is XmlToken }
?.castSafelyTo<XmlToken>()
?.takeIf { it.tokenType == XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN }
?.let { Twine.androidStringXmlReferenceRegex.find(it.text) }
?.groupValues
?.get(1)
?.let { collectTwineIdRefs(element, result, it) }
}
}
4 changes: 3 additions & 1 deletion src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
<colorSettingsPage implementation="by.overpass.twap.lang.style.highlighter.TwineColorSettingsPage"/>
<externalAnnotator language="Twine" implementationClass="by.overpass.twap.lang.style.annotator.TwineAnnotator"/>
<codeInsight.lineMarkerProvider language="JAVA"
implementationClass="by.overpass.twap.lang.TwineLineMarkerProvider"/>
implementationClass="by.overpass.twap.lang.navigation.JavaTwineLabelLineMarkerProvider"/>
<codeInsight.lineMarkerProvider language="XML"
implementationClass="by.overpass.twap.lang.navigation.XmlTwineLabelLineMarkerProvider"/>
<lang.elementManipulator forClass="by.overpass.twap.lang.parsing.psi.TwineIdentifier"
implementationClass="by.overpass.twap.lang.parsing.psi.TwineIdentifierManipulator"/>
<psi.referenceContributor implementation="by.overpass.twap.lang.reference.TwineReferenceContributor"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package by.overpass.twap.lang.navigation

import by.overpass.twap.Twine
import by.overpass.twap.lang.TwineFileType
import com.intellij.ide.highlighter.JavaFileType
import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase
import org.junit.Test

/**
* Test for [JavaTwineLabelLineMarkerProvider]
*/
class JavaTwineLabelLineMarkerProviderTest : JavaCodeInsightFixtureTestCase() {

/**
* Test that the twine gutter icon is present
*/
@Test
fun testGutterIconIsDisplayed() = with(myFixture) {
configureByText(
TwineFileType,
"""
[[section]]
[label1]
en = en
ru = ru
""".trimIndent()
)
configureByText(
JavaFileType.INSTANCE,
"""
package com.example;

public class Main {

public static void main(String[] args) {
int a = R.string.labe<caret>l1;
System.out.println(a);
}
}
""".trimIndent()
)

val gutterIcons = findGuttersAtCaret()
assertEquals(1, gutterIcons.size)
val twineGutterIcon = gutterIcons.first()
assertEquals(Twine.icon, twineGutterIcon.icon)
assertEquals(TwineLabelLineMarkerProvider.MESSAGE_NAVIGATE_TO_TWINE_LABEL, twineGutterIcon.tooltipText)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package by.overpass.twap.lang.navigation

import by.overpass.twap.Twine
import by.overpass.twap.lang.TwineFileType
import com.intellij.ide.highlighter.XmlFileType
import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase
import org.junit.Test

/**
* Test for [XmlTwineLabelLineMarkerProvider]
*/
class XmlTwineLabelLineMarkerProviderTest : JavaCodeInsightFixtureTestCase() {

private val testXmlFileContent = """
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label1.la<caret>b1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</FrameLayout>
""".trimIndent()

/**
* Test that the twine gutter icon is present
*/
@Test
fun testGutterIconIsVisible() = with(myFixture) {
configureByText(
TwineFileType,
"""
[[section]]
[label1.lab1]
en = en
ru = ru
""".trimIndent()
)
configureByText(
XmlFileType.INSTANCE,
testXmlFileContent
)

val gutterIcons = findGuttersAtCaret()
assertEquals(1, gutterIcons.size)
val twineGutterIcon = gutterIcons.first()
assertEquals(Twine.icon, twineGutterIcon.icon)
assertEquals(TwineLabelLineMarkerProvider.MESSAGE_NAVIGATE_TO_TWINE_LABEL, twineGutterIcon.tooltipText)
}
}