Skip to content

Commit

Permalink
Merge pull request #18 from dinbtechit/feature/5-Gutter-Icons-for-lin…
Browse files Browse the repository at this point in the history
…king-states-and-actions

Feature/5 gutter icons for linking states and actions
  • Loading branch information
dinbtechit authored Sep 8, 2023
2 parents a276fba + a17fd35 commit 1f91ae3
Show file tree
Hide file tree
Showing 10 changed files with 211 additions and 7 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
# ngxs Changelog

## [Unreleased]
### Added
- #5 - Gutter Icons for linking states and actions

## [0.0.2] - 2023-09-01

Expand Down
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,14 @@ NGXS is a state management library for Angular. This plugin provides NGXS CLI/Sc

# Features
- Simply right click -> New -> NGXS CLI/Schematics to generate a boiler plate store.
- Navigate to Action Implementation using Gutter Icons
- Many more coming soon. Checkout roadmap
> Please ensure you have [ngxs cli](https://www.ngxs.io/plugins/cli) installed either globally or at the project level.
# Roadmap

- [ ] NPM install ngxs package when does not exist
- [ ] Live Templates
- [ ] Intelli-sense for linking actions, state and selector

# Known bugs

- ngxs/cli - https://github.com/ngxs/cli/issues/9

<!-- Plugin description end -->

Expand Down
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pluginGroup = com.github.dinbtechit.ngxs
pluginName = ngxs
pluginRepositoryUrl = https://github.com/dinbtechit/ngxs
# SemVer format -> https://semver.org
pluginVersion = 0.0.2
pluginVersion = 0.0.3

# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
pluginSinceBuild = 223
Expand All @@ -16,7 +16,7 @@ platformVersion = 2022.3.3

# Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html
# Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22
platformPlugins = JavaScript
platformPlugins = JavaScript, PsiViewer:2022.3

# Gradle Releases -> https://github.com/gradle/gradle/releases
gradleVersion = 8.3
Expand Down
6 changes: 6 additions & 0 deletions src/main/kotlin/com/github/dinbtechit/ngxs/NgxsIcons.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,10 @@ object NgxsIcons {
val Donate = IconLoader.getIcon("icons/donate.svg", javaClass)
@JvmField
val GitHub = AllIcons.Vcs.Vendors.Github

object Gutter {
@JvmField
val Action = IconLoader.getIcon("icons/ngxs-action.svg", javaClass)
val MutipleActions = IconLoader.getIcon("icons/ngxs-multiple-action.svg", javaClass)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package com.github.dinbtechit.ngxs.action.editor

import com.github.dinbtechit.ngxs.NgxsIcons
import com.intellij.codeInsight.daemon.GutterIconNavigationHandler
import com.intellij.codeInsight.daemon.LineMarkerInfo
import com.intellij.codeInsight.daemon.LineMarkerProvider
import com.intellij.lang.javascript.psi.JSReferenceExpression
import com.intellij.lang.javascript.psi.ecma6.ES6Decorator
import com.intellij.lang.javascript.types.TypeScriptNewExpressionElementType
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.DefaultActionGroup
import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.actionSystem.impl.SimpleDataContext
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ScrollType
import com.intellij.openapi.editor.markup.GutterIconRenderer
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.OpenFileDescriptor
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.elementType
import javax.swing.JComponent

class NgxsActionLineMarkerIconProvider : LineMarkerProvider {
override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<PsiElement>? {

if (element.parent is JSReferenceExpression
&& element.parent.parent.elementType is TypeScriptNewExpressionElementType
&& element.parent.reference?.resolve()?.containingFile?.name?.contains(".actions.ts") == true
) {

val lineNumber = getLineNumber(element)
val elements = getAllPsiElementOnLine(element, lineNumber)

val navigateToElements = mutableSetOf<PsiElement>()
if (elements.isNotEmpty()) {
for (e in elements) {
navigateToElements.add(e)
}
}

if (elements.first() != element) return null

val icon = if (navigateToElements.size > 1) NgxsIcons.Gutter.MutipleActions
else NgxsIcons.Gutter.Action

val tooltipText = if (navigateToElements.size > 1) "NGXS Multiple Actions"
else "NGXS Action \"${element.text}\""

val clickAction = if (navigateToElements.size > 1)
createGroupGutterPopup(navigateToElements)
else createGutterNavigator(element)

return LineMarkerInfo(
element,
element.textRange,
icon, { tooltipText },
clickAction,
GutterIconRenderer.Alignment.RIGHT,
{ tooltipText }
)
}
return null
}

private fun createGroupGutterPopup(navigateToElements: MutableSet<PsiElement>): GutterIconNavigationHandler<PsiElement> {

return GutterIconNavigationHandler<PsiElement> { e, _ ->
val group = DefaultActionGroup()
for (navElement in navigateToElements) {
val action = object : AnAction({ "NGXS Action \"${navElement.text}\"" }, NgxsIcons.Gutter.Action) {
override fun actionPerformed(e: AnActionEvent) {
navigateToElement(navElement)
}
}
group.add(action)
}

val editor: Editor? = e.source as? Editor

val popup = JBPopupFactory.getInstance().createActionGroupPopup(
"",
group,
SimpleDataContext.builder().add(PlatformDataKeys.EDITOR, editor).build(),
JBPopupFactory.ActionSelectionAid.SPEEDSEARCH,
false
)
val component = e.component
if (component is JComponent) {
popup.showInScreenCoordinates(component, e.locationOnScreen)
}
}
}

private fun createGutterNavigator(element: PsiElement): GutterIconNavigationHandler<PsiElement> {
return GutterIconNavigationHandler<PsiElement> { _, _ ->
navigateToElement(element)
}
}

private fun navigateToElement(element: PsiElement) {
ApplicationManager.getApplication().runReadAction {
val element2 = element.parent.reference?.resolve()?.navigationElement
val refs = ReferencesSearch.search(element2!!).findAll()
for (ref in refs.toList()) {
val actionDecoratorElement = PsiTreeUtil.findFirstParent(ref.element) { it is ES6Decorator }
val hasActionDecorator = actionDecoratorElement != null
if (hasActionDecorator &&
ref.element.containingFile.name.contains(".state.ts")
) {
val fileEditorManager = FileEditorManager.getInstance(element.project)
val textEditor = fileEditorManager.openTextEditor(
OpenFileDescriptor(
element.project,
ref.element.containingFile.virtualFile
), true
)
val start = ref.element.textRange.startOffset
textEditor?.caretModel?.moveToOffset(start)
textEditor?.scrollingModel?.scrollToCaret(ScrollType.MAKE_VISIBLE)
}
}
}
}

private fun getLineNumber(element: PsiElement): Int {
val psiFile: PsiFile = element.containingFile
val document: Document = FileDocumentManager.getInstance().getDocument(psiFile.virtualFile)!!
return document.getLineNumber(element.textRange.startOffset)
}


private fun getAllPsiElementOnLine(currentPsiElement: PsiElement, lineNumber: Int): List<PsiElement> {
val psiFile: PsiFile = currentPsiElement.containingFile
val document: Document = FileDocumentManager.getInstance().getDocument(psiFile.virtualFile)!!

val res = mutableListOf<PsiElement>()
val startOffset = document.getLineStartOffset(lineNumber)
val endOffset = document.getLineEndOffset(lineNumber)

var currentOffset = startOffset
while (currentOffset <= endOffset) {
val element = psiFile.findElementAt(currentOffset)

if (element != null) {
if (element.parent is JSReferenceExpression
&& element.parent.parent.elementType is TypeScriptNewExpressionElementType
&& element.parent.reference?.resolve()?.containingFile?.name?.contains(".actions.ts") == true
) {
res.add(element)
}
currentOffset = element.textRange.endOffset
} else {
break
}
}
return res
}


}
4 changes: 4 additions & 0 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
<postStartupActivity implementation="com.github.dinbtechit.ngxs.startup.MyStartupNotifyActivity"/>
<applicationService
serviceImplementation="com.github.dinbtechit.ngxs.settings.SettingsStore"/>
<codeInsight.lineMarkerProvider
language="TypeScript"
id="com.github.dinbtechit.ngxs.action.editor.NgxsActionLineMarkerIconProvider"
implementationClass="com.github.dinbtechit.ngxs.action.editor.NgxsActionLineMarkerIconProvider"/>
<notificationGroup id="NGXS Notification Group" displayType="STICKY_BALLOON"/>
<errorHandler implementation="com.github.dinbtechit.ngxs.diagostic.MyErrorReportSubmitter"/>
</extensions>
Expand Down
6 changes: 6 additions & 0 deletions src/main/resources/icons/ngxs-action.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions src/main/resources/icons/ngxs-action_dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions src/main/resources/icons/ngxs-multiple-action.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions src/main/resources/icons/ngxs-multiple-action_dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 1f91ae3

Please sign in to comment.