1 // Copyright 2000-2020 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.
2 package training.learn.lesson.general.navigation
4 import com.intellij.codeInsight.TargetElementUtil
5 import com.intellij.openapi.actionSystem.impl.ActionMenuItem
6 import com.intellij.openapi.fileEditor.FileEditorManager
7 import com.intellij.openapi.wm.impl.content.BaseLabel
8 import com.intellij.psi.PsiDocumentManager
9 import com.intellij.psi.PsiElement
10 import com.intellij.psi.PsiFile
11 import com.intellij.testGuiFramework.framework.GuiTestUtil.shortcut
12 import com.intellij.testGuiFramework.util.Key
13 import com.intellij.ui.UIBundle
14 import com.intellij.ui.table.JBTable
15 import training.commands.kotlin.TaskRuntimeContext
16 import training.learn.LearnBundle
17 import training.learn.LessonsBundle
18 import training.learn.interfaces.Module
19 import training.learn.lesson.kimpl.KLesson
20 import training.learn.lesson.kimpl.LessonContext
21 import training.learn.lesson.kimpl.closeAllFindTabs
23 abstract class DeclarationAndUsagesLesson(module: Module, lang: String)
24 : KLesson("Declaration and usages", LessonsBundle.message("declaration.and.usages.lesson.name"), module, lang) {
25 abstract fun LessonContext.setInitialPosition()
26 abstract val typeOfEntity: Int // 0 - method, 1 - attribute accessor
27 abstract override val existedFile: String
29 override val lessonContent: LessonContext.() -> Unit
33 task("GotoDeclaration") {
34 text(LessonsBundle.message("declaration.and.usages.jump.to.declaration", action(it), typeOfEntity))
35 trigger(it, { state() }) { before, _ ->
36 before != null && !isInsidePsi(before.target.navigationElement, before.position)
41 task("GotoDeclaration") {
42 text(LessonsBundle.message("declaration.and.usages.show.usages", typeOfEntity, action(it)))
43 trigger(it, { state() }) l@{ before, now ->
44 if (before == null || now == null) {
48 val navigationElement = before.target.navigationElement
49 return@l navigationElement == now.target.navigationElement &&
50 isInsidePsi(navigationElement, before.position) &&
51 !isInsidePsi(navigationElement, now.position)
56 waitComponent(JBTable::class.java, "ShowUsagesTable")
66 text(LessonsBundle.message("declaration.and.usages.find.usages", action(it)))
68 triggerByUiComponentAndHighlight { ui: BaseLabel ->
69 ui.text?.contains(LearnBundle.message("usages.tab.name")) ?: false
76 val pinTabText = UIBundle.message("tabbed.pane.pin.tab.action.name")
80 previous.ui?.let { usagesTab -> jComponent(usagesTab).rightClick() }
83 triggerByUiComponentAndHighlight(highlightInside = false) { ui: ActionMenuItem ->
84 ui.text?.contains(pinTabText) ?: false
87 text(LessonsBundle.message("declaration.and.usages.pin.motivation", strong(UIBundle.message("tool.window.name.find"))))
88 text(LessonsBundle.message("declaration.and.usages.right.click.tab", strong(LearnBundle.message("usages.tab.name"))))
91 task("PinToolwindowTab") {
94 text(LessonsBundle.message("declaration.and.usages.select.pin.item", strong(pinTabText)))
97 jComponent(previous.ui!!).click()
102 actionTask("HideActiveWindow") {
103 LessonsBundle.message("declaration.and.usages.hide.view", action(it))
106 actionTask("ActivateFindToolWindow") {
107 LessonsBundle.message("declaration.and.usages.open.find.view",
108 action(it), strong(UIBundle.message("tool.window.name.find")))
112 private fun TaskRuntimeContext.state(): MyInfo? {
113 val flags = TargetElementUtil.ELEMENT_NAME_ACCEPTED or TargetElementUtil.REFERENCED_ELEMENT_ACCEPTED
115 val currentEditor = FileEditorManager.getInstance(project).selectedTextEditor ?: return null
117 val target = TargetElementUtil.findTargetElement(currentEditor, flags) ?: return null
119 val file = PsiDocumentManager.getInstance(project).getPsiFile(currentEditor.document) ?: return null
120 val position = MyPosition(file,
121 currentEditor.caretModel.offset)
123 return MyInfo(target, position)
126 private fun isInsidePsi(psi: PsiElement, position: MyPosition): Boolean {
127 return psi.containingFile == position.file && psi.textRange.contains(position.offset)
130 private data class MyInfo(val target: PsiElement, val position: MyPosition)
132 private data class MyPosition(val file: PsiFile, val offset: Int)