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
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.util.messages.Topic
16 import com.intellij.xdebugger.XDebuggerManager
17 import training.commands.kotlin.TaskContext
18 import training.commands.kotlin.TaskRuntimeContext
19 import training.keymap.KeymapUtil
20 import training.learn.LearnBundle
21 import java.awt.event.KeyEvent
22 import javax.swing.JList
23 import javax.swing.KeyStroke
26 val productName: String
27 get() = ApplicationNamesInfo.getInstance().fullProductName
29 fun insertIntoSample(sample: LessonSample, inserted: String): String {
30 return sample.text.substring(0, sample.startOffset) + inserted + sample.text.substring(sample.startOffset)
33 fun TaskRuntimeContext.checkPositionOfEditor(sample: LessonSample): TaskContext.RestoreNotification? {
34 fun invalidCaret(): Boolean {
35 val selection = sample.selection
36 val currentCaret = editor.caretModel.currentCaret
37 return if (selection != null && selection.first != selection.second) {
38 currentCaret.selectionStart != selection.first || currentCaret.selectionEnd != selection.second
40 else currentCaret.offset != sample.startOffset
43 return checkExpectedStateOfEditor(sample, false)
44 ?: if (invalidCaret()) sampleRestoreNotification(TaskContext.CaretRestoreProposal, sample) else null
47 fun TaskRuntimeContext.checkExpectedStateOfEditor(sample: LessonSample,
48 checkPosition: Boolean = true,
49 checkModification: (String) -> Boolean = { it.isEmpty() }): TaskContext.RestoreNotification? {
50 val prefix = sample.text.substring(0, sample.startOffset)
51 val postfix = sample.text.substring(sample.startOffset)
53 val docText = editor.document.charsSequence
54 val message = if (docText.startsWith(prefix) && docText.endsWith(postfix)) {
55 val middle = docText.subSequence(prefix.length, docText.length - postfix.length).toString()
56 if (checkModification(middle)) {
57 val offset = editor.caretModel.offset
58 if (!checkPosition || (prefix.length <= offset && offset <= prefix.length + middle.length)) {
62 TaskContext.CaretRestoreProposal
66 TaskContext.ModificationRestoreProposal
70 TaskContext.ModificationRestoreProposal
73 return if (message != null) sampleRestoreNotification(message, sample) else null
76 fun TaskRuntimeContext.sampleRestoreNotification(message: String, sample: LessonSample) =
77 TaskContext.RestoreNotification(message) { setSample(sample) }
79 fun findItem(ui: JList<*>, checkList: (item: Any) -> Boolean): Int? {
80 for (i in 0 until ui.model.size) {
81 val elementAt = ui.model.getElementAt(i)
82 if (checkList(elementAt)) {
89 fun setEditorReadOnly(editor: Editor) {
90 if (editor !is EditorEx) return
91 editor.isViewer = true
92 EditorModificationUtil.setReadOnlyHint(editor, LearnBundle.message("learn.task.read.only.hint"))
95 fun actionName(actionId: String): @NlsActions.ActionText String {
96 val name = ActionManager.getInstance().getAction(actionId).templatePresentation.text ?: error("No action with ID $actionId")
97 return "<strong>${name}</strong>"
100 fun rawEnter(): String {
101 val keyStrokeEnter = KeymapUtil.getKeyStrokeText(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0))
102 return "<raw_action>$keyStrokeEnter</raw_action>"
105 fun rawCtrlEnter(): String {
106 return "<raw_action>${if (SystemInfo.isMacOSMojave) "\u2318\u23CE" else "Ctrl + Enter"}</raw_action>"
110 fun TaskContext.toolWindowShowed(toolWindowId: String) {
112 subscribeForMessageBus(ToolWindowManagerListener.TOPIC, object: ToolWindowManagerListener {
113 override fun toolWindowShown(toolWindow: ToolWindow) {
114 if (toolWindow.id == toolWindowId)
121 fun <L> TaskRuntimeContext.subscribeForMessageBus(topic: Topic<L>, handler: L) {
122 project.messageBus.connect(taskDisposable).subscribe(topic, handler)
125 fun TaskRuntimeContext.lineWithBreakpoints(): Set<Int> {
126 val breakpointManager = XDebuggerManager.getInstance(project).breakpointManager
127 return breakpointManager.allBreakpoints.filter {
128 val file = FileDocumentManager.getInstance().getFile(editor.document)
129 it.sourcePosition?.file == file
131 it.sourcePosition?.line
136 * @param [restoreId] where to restore, `null` means the previous task
137 * @param [restoreRequired] returns true iff restore is needed
139 fun TaskContext.restoreAfterStateBecomeFalse(restoreId: TaskContext.TaskId? = null, restoreRequired: TaskRuntimeContext.() -> Boolean) {
140 var restoreIsPossible = false
141 restoreState(restoreId) {
142 val required = restoreRequired()
143 (restoreIsPossible && required).also { restoreIsPossible = restoreIsPossible || !required }
147 fun String.dropMnemonic(): String {
148 return TextWithMnemonic.parse(this).dropMnemonic(true).text