[IFT] Add utility method to close find tabs
[idea/contrib.git] / ide-features-trainer / src / training / learn / lesson / kimpl / LessonUtil.kt
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.kimpl
3
4 import com.intellij.openapi.actionSystem.ActionManager
5 import com.intellij.openapi.application.ApplicationNamesInfo
6 import com.intellij.openapi.editor.Editor
7 import com.intellij.openapi.editor.EditorModificationUtil
8 import com.intellij.openapi.editor.ex.EditorEx
9 import com.intellij.openapi.fileEditor.FileDocumentManager
10 import com.intellij.openapi.util.NlsActions
11 import com.intellij.openapi.util.SystemInfo
12 import com.intellij.openapi.util.text.TextWithMnemonic
13 import com.intellij.openapi.wm.ToolWindow
14 import com.intellij.openapi.wm.ex.ToolWindowManagerListener
15 import com.intellij.ui.content.Content
16 import com.intellij.usageView.UsageViewContentManager
17 import com.intellij.util.messages.Topic
18 import com.intellij.xdebugger.XDebuggerManager
19 import training.commands.kotlin.TaskContext
20 import training.commands.kotlin.TaskRuntimeContext
21 import training.keymap.KeymapUtil
22 import training.learn.LearnBundle
23 import java.awt.event.KeyEvent
24 import javax.swing.JList
25 import javax.swing.KeyStroke
26
27 object LessonUtil {
28   val productName: String
29     get() = ApplicationNamesInfo.getInstance().fullProductName
30
31   fun insertIntoSample(sample: LessonSample, inserted: String): String {
32     return sample.text.substring(0, sample.startOffset) + inserted + sample.text.substring(sample.startOffset)
33   }
34
35   fun TaskRuntimeContext.checkPositionOfEditor(sample: LessonSample): TaskContext.RestoreNotification? {
36     fun invalidCaret(): Boolean {
37       val selection = sample.selection
38       val currentCaret = editor.caretModel.currentCaret
39       return if (selection != null && selection.first != selection.second) {
40         currentCaret.selectionStart != selection.first || currentCaret.selectionEnd != selection.second
41       }
42       else currentCaret.offset != sample.startOffset
43     }
44
45     return checkExpectedStateOfEditor(sample, false)
46            ?: if (invalidCaret()) sampleRestoreNotification(TaskContext.CaretRestoreProposal, sample) else null
47   }
48
49   fun TaskRuntimeContext.checkExpectedStateOfEditor(sample: LessonSample,
50                                                     checkPosition: Boolean = true,
51                                                     checkModification: (String) -> Boolean = { it.isEmpty() }): TaskContext.RestoreNotification? {
52     val prefix = sample.text.substring(0, sample.startOffset)
53     val postfix = sample.text.substring(sample.startOffset)
54
55     val docText = editor.document.charsSequence
56     val message = if (docText.startsWith(prefix) && docText.endsWith(postfix)) {
57       val middle = docText.subSequence(prefix.length, docText.length - postfix.length).toString()
58       if (checkModification(middle)) {
59         val offset = editor.caretModel.offset
60         if (!checkPosition || (prefix.length <= offset && offset <= prefix.length + middle.length)) {
61           null
62         }
63         else {
64           TaskContext.CaretRestoreProposal
65         }
66       }
67       else {
68         TaskContext.ModificationRestoreProposal
69       }
70     }
71     else {
72       TaskContext.ModificationRestoreProposal
73     }
74
75     return if (message != null) sampleRestoreNotification(message, sample) else null
76   }
77
78   fun TaskRuntimeContext.sampleRestoreNotification(message: String, sample: LessonSample) =
79     TaskContext.RestoreNotification(message) { setSample(sample) }
80
81   fun findItem(ui: JList<*>, checkList: (item: Any) -> Boolean): Int? {
82     for (i in 0 until ui.model.size) {
83       val elementAt = ui.model.getElementAt(i)
84       if (checkList(elementAt)) {
85         return i
86       }
87     }
88     return null
89   }
90
91   fun setEditorReadOnly(editor: Editor) {
92     if (editor !is EditorEx) return
93     editor.isViewer = true
94     EditorModificationUtil.setReadOnlyHint(editor, LearnBundle.message("learn.task.read.only.hint"))
95   }
96
97   fun actionName(actionId: String): @NlsActions.ActionText String {
98     val name = ActionManager.getInstance().getAction(actionId).templatePresentation.text ?: error("No action with ID $actionId")
99     return "<strong>${name}</strong>"
100   }
101
102   fun rawEnter(): String {
103     val keyStrokeEnter = KeymapUtil.getKeyStrokeText(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0))
104     return "<raw_action>$keyStrokeEnter</raw_action>"
105   }
106
107   fun rawCtrlEnter(): String {
108     return "<raw_action>${if (SystemInfo.isMacOSMojave) "\u2318\u23CE" else "Ctrl + Enter"}</raw_action>"
109   }
110 }
111
112 fun TaskContext.toolWindowShowed(toolWindowId: String) {
113   addFutureStep {
114     subscribeForMessageBus(ToolWindowManagerListener.TOPIC, object: ToolWindowManagerListener {
115       override fun toolWindowShown(toolWindow: ToolWindow) {
116         if (toolWindow.id == toolWindowId)
117           completeStep()
118       }
119     })
120   }
121 }
122
123 fun <L> TaskRuntimeContext.subscribeForMessageBus(topic: Topic<L>, handler: L) {
124   project.messageBus.connect(taskDisposable).subscribe(topic, handler)
125 }
126
127 fun TaskRuntimeContext.lineWithBreakpoints(): Set<Int> {
128   val breakpointManager = XDebuggerManager.getInstance(project).breakpointManager
129   return breakpointManager.allBreakpoints.filter {
130     val file = FileDocumentManager.getInstance().getFile(editor.document)
131     it.sourcePosition?.file == file
132   }.mapNotNull {
133     it.sourcePosition?.line
134   }.toSet()
135 }
136
137 /**
138  * @param [restoreId] where to restore, `null` means the previous task
139  * @param [restoreRequired] returns true iff restore is needed
140  */
141 fun TaskContext.restoreAfterStateBecomeFalse(restoreId: TaskContext.TaskId? = null, restoreRequired: TaskRuntimeContext.() -> Boolean) {
142   var restoreIsPossible = false
143   restoreState(restoreId) {
144     val required = restoreRequired()
145     (restoreIsPossible && required).also { restoreIsPossible = restoreIsPossible || !required }
146   }
147 }
148
149 fun TaskRuntimeContext.closeAllFindTabs() {
150   val usageViewManager = UsageViewContentManager.getInstance(project)
151   var selectedContent: Content?
152   while (usageViewManager.selectedContent.also { selectedContent = it } != null) {
153     usageViewManager.closeContent(selectedContent!!)
154   }
155 }
156
157 fun String.dropMnemonic(): String {
158   return TextWithMnemonic.parse(this).dropMnemonic(true).text
159 }