PK DT HowToCreateLessons.md###Paths
We should be careful with paths of all lessons and _xml files_ that we are using to hold lessons data.
If you need to add support for a custom language you should:
1. Add extension for `training.lang.LangSupport`
2. Add your _LearnProject_ realisation and put it in `res/learnProjects/$langName$`
3. Specify _LearnProject_ name in `LangSupport`
How is plugin looking for a modules: `res/data/modules.xml`. This file contains a relative path from modules
dir to a _module xml_.
```xml
```
_Module xml_ has a list of lessons with a relative path to them:
```xml
```
So lessons for _Refactorings_ module are located in `res/data/modules/java/Refactorings/01.Rename.xml`.
PK ک " training/FeaturesTrainerIcons.java// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package training;
import com.intellij.ui.IconManager;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
/**
* NOTE THIS FILE IS AUTO-GENERATED
* DO NOT EDIT IT BY HAND, run "Generate icon classes" configuration instead
*/
public final class FeaturesTrainerIcons {
private static @NotNull Icon load(@NotNull String path, int cacheKey, int flags) {
return IconManager.getInstance().loadRasterizedIcon(path, FeaturesTrainerIcons.class.getClassLoader(), cacheKey, flags);
}
/** 16x16 */ public static final @NotNull Icon Checkmark = load("img/checkmark.svg", 1210931315, 2);
/** 16x16 */ public static final @NotNull Icon FeatureTrainer = load("img/featureTrainer.svg", 1806467053, 2);
/** 13x13 */ public static final @NotNull Icon FeatureTrainerToolWindow = load("img/featureTrainerToolWindow.svg", -68627899, 2);
/** 16x16 */ public static final @NotNull Icon GreenCheckmark = load("img/greenCheckmark.svg", 1355926933, 2);
/** 16x16 */ public static final @NotNull Icon PluginIcon = load("img/pluginIcon.svg", -1574300806, 0);
/** 16x16 */ public static final @NotNull Icon ResetLesson = load("img/resetLesson.svg", 1838614018, 2);
}
PK `;(k k % training/actions/AutorunAllLessons.kt// 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.
package training.actions
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import training.learn.lesson.LessonStateManager
import training.util.LearningLessonsAutoExecutor
private class AutorunAllLessons : AnAction() {
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
LessonStateManager.resetPassedStatus()
LearningLessonsAutoExecutor.runAllLessons(project)
}
override fun getActionUpdateThread(): ActionUpdateThread {
return ActionUpdateThread.BGT
}
override fun update(e: AnActionEvent) {
e.presentation.isEnabledAndVisible = e.project != null
}
}PK 0kQ ( training/actions/AutorunCurrentLesson.kt// 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.
package training.actions
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import training.learn.lesson.LessonManager
import training.util.LearningLessonsAutoExecutor
private class AutorunCurrentLesson : AnAction() {
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
val currentLesson = LessonManager.instance.currentLesson ?: return
LearningLessonsAutoExecutor.runSingleLesson(project, currentLesson)
}
override fun getActionUpdateThread() = ActionUpdateThread.EDT
override fun update(e: AnActionEvent) {
e.presentation.isEnabled = LessonManager.instance.currentLesson != null
}
}PK vSr
> training/actions/ChooseProgrammingLanguageForLearningAction.kt// 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.
package training.actions
import com.intellij.lang.Language
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.actionSystem.ex.ComboBoxAction
import com.intellij.openapi.util.NlsSafe
import com.intellij.ui.ExperimentalUI
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.UIUtil
import training.lang.LangManager
import training.lang.LangSupport
import training.lang.LangSupportBean
import training.learn.LearnBundle
import training.ui.LearnToolWindow
import training.util.resetPrimaryLanguage
import javax.swing.JComponent
internal class ChooseProgrammingLanguageForLearningAction(private val learnToolWindow: LearnToolWindow) : ComboBoxAction() {
override fun getActionUpdateThread(): ActionUpdateThread {
return ActionUpdateThread.BGT
}
override fun createPopupActionGroup(button: JComponent, context: DataContext): DefaultActionGroup {
val allActionsGroup = DefaultActionGroup()
val supportedLanguagesExtensions = LangManager.getInstance().supportedLanguagesExtensions.sortedBy { it.language }
for (langSupportExt: LangSupportBean in supportedLanguagesExtensions) {
val languageId = langSupportExt.getLang()
val displayName = Language.findLanguageByID(languageId)?.displayName ?: continue
allActionsGroup.add(SelectLanguageAction(languageId, displayName))
}
return allActionsGroup
}
override fun update(e: AnActionEvent) {
val langSupport = LangManager.getInstance().getLangSupport()
if (langSupport != null) {
e.presentation.text = getDisplayName(langSupport)
}
e.presentation.description = LearnBundle.message("learn.choose.language.description.combo.box")
}
override fun createCustomComponent(presentation: Presentation, place: String): JComponent {
return super.createCustomComponent(presentation, place).also {
if (ExperimentalUI.isNewUI()) {
UIUtil.setBackgroundRecursively(it, JBUI.CurrentTheme.ToolWindow.background())
}
}
}
private inner class SelectLanguageAction(private val languageId: String, @NlsSafe displayName: String) : AnAction(displayName) {
override fun actionPerformed(e: AnActionEvent) {
val ep = LangManager.getInstance().supportedLanguagesExtensions.singleOrNull { it.language == languageId } ?: return
resetPrimaryLanguage(ep.getLang())
learnToolWindow.setModulesPanel()
}
}
}
@NlsSafe
private fun getDisplayName(language: LangSupport) =
Language.findLanguageByID(language.primaryLanguage)?.displayName ?: LearnBundle.message("unknown.language.name")
PK /:
+ training/actions/DumpFeaturesTrainerText.kt// 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.
@file:Suppress("HardCodedStringLiteral")
package training.actions
import com.intellij.ide.CopyPasteManagerEx
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.ui.dsl.builder.bind
import com.intellij.ui.dsl.builder.panel
import training.dsl.LearningBalloonConfig
import training.dsl.LessonContext
import training.dsl.RuntimeTextContext
import training.dsl.TaskContext
import training.dsl.impl.LessonExecutorUtil
import training.learn.CourseManager
import training.learn.course.KLesson
import java.awt.datatransfer.StringSelection
import javax.swing.JComponent
private enum class IftDumpMode { TEXT_ONLY, CODE_POSITIONS }
private class DumpFeaturesTrainerText : DumbAwareAction() {
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
val dialog = object : DialogWrapper(project) {
var mode = IftDumpMode.TEXT_ONLY
override fun createCenterPanel(): JComponent = panel {
buttonsGroup {
row {
radioButton("Text only", IftDumpMode.TEXT_ONLY)
radioButton("Code positions", IftDumpMode.CODE_POSITIONS)
}
}.bind(::mode)
}
init {
init()
title = "What Do You Want to Copy?"
}
}
dialog.show()
val lessonsForModules = CourseManager.instance.lessonsForModules
val buffer = StringBuffer()
for (x in lessonsForModules) {
if (x is KLesson) {
buffer.append(x.name)
buffer.append(":\n")
x.fullLessonContent(ApplyTaskLessonContext(buffer, project, dialog.mode, x))
buffer.append('\n')
}
}
CopyPasteManagerEx.getInstance().setContents(StringSelection(buffer.toString()))
}
override fun getActionUpdateThread(): ActionUpdateThread {
return ActionUpdateThread.BGT
}
override fun update(e: AnActionEvent) {
e.presentation.isEnabledAndVisible = e.project != null
}
}
private class TextCollector(private val buffer: StringBuffer, override val project: Project) : TaskContext() {
override fun text(text: String, useBalloon: LearningBalloonConfig?) {
buffer.append(text)
buffer.append('\n')
}
override fun runtimeText(callback: RuntimeTextContext.() -> String?) {
// TODO: think how to dump it
}
}
private class ApplyTaskLessonContext(private val buffer: StringBuffer,
private val project: Project,
private val mode: IftDumpMode,
override val lesson: KLesson) : LessonContext() {
private var internalTaskNumber = 0
private var taskVisualIndex = 1
override fun task(taskContent: TaskContext.() -> Unit) {
buffer.append("($internalTaskNumber -> $taskVisualIndex) ")
if (mode == IftDumpMode.TEXT_ONLY) {
val taskContext: TaskContext = TextCollector(buffer, project)
taskContent(taskContext)
}
if (mode == IftDumpMode.CODE_POSITIONS) {
buffer.append(LessonExecutorUtil.getTaskCallInfo())
buffer.append('\n')
}
val taskProperties = LessonExecutorUtil.taskProperties(taskContent, project)
if (taskProperties.hasDetection && taskProperties.messagesNumber > 0) {
taskVisualIndex++
}
internalTaskNumber++
}
}PK \ڜ6 6 $ training/actions/NextLessonAction.kt// 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.
package training.actions
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import training.learn.CourseManager
import training.statistic.LessonStartingWay
import training.statistic.StatisticBase
import training.util.getLearnToolWindowForProject
import training.util.getNextLessonForCurrent
import training.util.lessonOpenedInProject
private class NextLessonAction : AnAction() {
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
if (getLearnToolWindowForProject(project) == null) return
val nextLesson = getNextLessonForCurrent() ?: return
StatisticBase.logLessonStopped(StatisticBase.LessonStopReason.OPEN_NEXT_OR_PREV_LESSON)
CourseManager.instance.openLesson(project, nextLesson, LessonStartingWay.NEXT_BUTTON)
}
override fun getActionUpdateThread() = ActionUpdateThread.EDT
override fun update(e: AnActionEvent) {
val project = e.project
val lesson = lessonOpenedInProject(project)
e.presentation.isEnabled = lesson != null && CourseManager.instance.lessonsForModules.lastOrNull() != lesson
}
}
PK NрX X " training/actions/OpenLearnPanel.kt// 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.
package training.actions
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.project.DumbAwareAction
import training.learn.OpenLessonActivities
import training.util.learningToolWindow
private class OpenLearnPanel : DumbAwareAction() {
override fun actionPerformed(e: AnActionEvent) {
val project = e.project
if (project != null) {
val toolWindow = learningToolWindow(project) ?: return
toolWindow.show()
}
else {
OpenLessonActivities.openLearnProjectFromWelcomeScreen(null)
}
}
override fun getActionUpdateThread(): ActionUpdateThread {
return ActionUpdateThread.BGT
}
}
PK 7A A ( training/actions/PreviousLessonAction.kt// 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.
package training.actions
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import training.learn.CourseManager
import training.statistic.LessonStartingWay
import training.statistic.StatisticBase
import training.util.getLearnToolWindowForProject
import training.util.getPreviousLessonForCurrent
import training.util.lessonOpenedInProject
private class PreviousLessonAction : AnAction() {
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
if (getLearnToolWindowForProject(project) == null) return
val previousLesson = getPreviousLessonForCurrent()
StatisticBase.logLessonStopped(StatisticBase.LessonStopReason.OPEN_NEXT_OR_PREV_LESSON)
CourseManager.instance.openLesson(project, previousLesson, LessonStartingWay.PREV_BUTTON)
}
override fun getActionUpdateThread() = ActionUpdateThread.EDT
override fun update(e: AnActionEvent) {
val project = e.project
val lesson = lessonOpenedInProject(project)
e.presentation.isEnabled = lesson != null && CourseManager.instance.lessonsForModules.firstOrNull() != lesson
}
}
PK Ӑ / training/actions/ResetLearningProgressAction.kt// 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.
package training.actions
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.ui.showYesNoDialog
import training.learn.LearnBundle
import training.util.clearTrainingProgress
private class ResetLearningProgressAction : AnAction() {
override fun actionPerformed(e: AnActionEvent) {
if (showYesNoDialog(LearnBundle.message("learn.option.reset.progress.dialog"),
LearnBundle.message("learn.option.reset.progress.confirm"), null)) {
clearTrainingProgress()
}
}
override fun getActionUpdateThread(): ActionUpdateThread {
return ActionUpdateThread.BGT
}
override fun update(e: AnActionEvent) {
e.presentation.isEnabledAndVisible = e.project != null
}
}
PK x x 6 training/actions/ResetOnboardingFeedbackStateAction.kt// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package training.actions
import com.intellij.ide.util.PropertiesComponent
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.project.DumbAwareAction
import training.lang.LangManager
import training.ui.getFeedbackProposedPropertyName
class ResetOnboardingFeedbackStateAction : DumbAwareAction() {
override fun actionPerformed(e: AnActionEvent) {
val langSupport = LangManager.getInstance().getLangSupport() ?: error("Lang support is null for some magic reason")
val propertyName = getFeedbackProposedPropertyName(langSupport)
PropertiesComponent.getInstance().setValue(propertyName, false)
}
override fun getActionUpdateThread() = ActionUpdateThread.BGT
}PK GtQ Q ' training/actions/RestartLessonAction.kt// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package training.actions
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import training.learn.CourseManager
import training.learn.lesson.LessonManager
import training.statistic.LessonStartingWay
import training.statistic.StatisticBase
import training.ui.LearningUiManager
private class RestartLessonAction : AnAction() {
override fun actionPerformed(e: AnActionEvent) {
val activeToolWindow = LearningUiManager.activeToolWindow ?: return
val lesson = LessonManager.instance.currentLesson ?: return
StatisticBase.logLessonStopped(StatisticBase.LessonStopReason.RESTART)
LessonManager.instance.stopLesson()
lesson.module.primaryLanguage?.let { it.onboardingFeedbackData = null }
CourseManager.instance.openLesson(activeToolWindow.project, lesson, LessonStartingWay.RESTART_BUTTON)
}
override fun getActionUpdateThread() = ActionUpdateThread.EDT
override fun update(e: AnActionEvent) {
val activeToolWindow = LearningUiManager.activeToolWindow
e.presentation.isEnabled = activeToolWindow != null && activeToolWindow.project == e.project
}
}
PK LКQ , training/actions/SetCurrentLessonAsPassed.kt// 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.
package training.actions
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import training.learn.lesson.LessonManager
private class SetCurrentLessonAsPassed : AnAction() {
override fun actionPerformed(e: AnActionEvent) {
val currentLesson = LessonManager.instance.currentLesson ?: return
LessonManager.instance.passLesson(currentLesson)
}
override fun getActionUpdateThread() = ActionUpdateThread.EDT
override fun update(e: AnActionEvent) {
e.presentation.isEnabled = LessonManager.instance.currentLesson != null
}
}PK aSq q 1 training/dsl/HighlightTriggerParametersContext.kt// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package training.dsl
class HighlightTriggerParametersContext internal constructor() {
var highlightBorder: Boolean = false
var highlightInside: Boolean = false
var usePulsation: Boolean = false
var clearPreviousHighlights: Boolean = true
/**
* Whether to limit highlighting by the component's visible area.
* Use it in the rare cases when it is convenient to look for a component "connected" with another one (like editor and its gutter).
*/
var limitByVisibleRect: Boolean = true
}PK y{ * training/dsl/HighlightingTriggerMethods.kt// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package training.dsl
import org.jetbrains.annotations.ApiStatus
import java.awt.Component
import java.awt.Rectangle
import javax.swing.JTree
import javax.swing.tree.TreePath
abstract class HighlightingTriggerMethods internal constructor() {
inline fun withSelector(
noinline selector: ((candidates: Collection) -> ComponentType?)
): HighlightingTriggerMethodsWithType {
@Suppress("DEPRECATION")
return HighlightingTriggerMethodsWithType(ComponentType::class.java, this, selector)
}
inline fun component(crossinline finderFunction: TaskRuntimeContext.(ComponentType) -> Boolean) {
@Suppress("DEPRECATION")
explicitComponentDetection(ComponentType::class.java, null) { finderFunction(it) }
}
inline fun componentPart(crossinline rectangle: TaskRuntimeContext.(ComponentType) -> Rectangle?) {
@Suppress("DEPRECATION")
explicitComponentPartDetection(ComponentType::class.java) { rectangle(it) }
}
open fun treeItem(checkPath: TaskRuntimeContext.(tree: JTree, path: TreePath) -> Boolean) = Unit
open fun listItem(checkList: TaskRuntimeContext.(item: Any) -> Boolean) = Unit
@Deprecated("Use inline version")
open fun explicitComponentDetection(
componentClass: Class,
selector: ((candidates: Collection) -> ComponentType?)?,
finderFunction: TaskRuntimeContext.(ComponentType) -> Boolean
) = Unit
@Deprecated("Use inline version")
@ApiStatus.ScheduledForRemoval
open fun explicitComponentPartDetection(
componentClass: Class,
rectangle: TaskRuntimeContext.(ComponentType) -> Rectangle?
) = Unit
}
class HighlightingTriggerMethodsWithType
@Deprecated("Do not use directly")
@ApiStatus.ScheduledForRemoval
constructor(
val componentClass: Class,
val parent: HighlightingTriggerMethods,
val selector: (candidates: Collection) -> ComponentType?
) {
inline fun byComponent(crossinline finderFunction: TaskRuntimeContext.(ComponentType) -> Boolean) {
@Suppress("DEPRECATION")
parent.explicitComponentDetection(componentClass, selector) { finderFunction(it) }
}
}
PK [ % training/dsl/LearningBalloonConfig.kt// 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.
package training.dsl
import com.intellij.ide.IdeBundle
import com.intellij.openapi.ui.popup.Balloon
import org.jetbrains.annotations.Nls
import javax.swing.JComponent
data class LearningBalloonConfig(
val side: Balloon.Position,
/**
* 0 means to use default width from GotIt [com.intellij.ui.GotItComponentBuilder.Companion.MAX_WIDTH].
* It is the maximum width before scaling
*/
val width: Int,
val duplicateMessage: Boolean = false,
val highlightingComponent: JComponent? = null,
val delayBeforeShow: Int = 0,
val animationCycle: Int = 0,
/** -1 means to place the pointer in the center of the balloon. It is the distance before scaling. */
val cornerToPointerDistance: Int = -1,
/** button will be present only if [gotItCallBack] is not null */
val buttonText: @Nls String = IdeBundle.message("got.it.button.name"),
val gotItCallBack: (() -> Unit)? = null
)PK |ԩ training/dsl/LearningDsl.kt// Copyright 2000-2021 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.
package training.dsl
@DslMarker
annotation class LearningDsl
PK : training/dsl/LearningDslBase.kt// Copyright 2000-2021 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.
package training.dsl
import com.intellij.icons.AllIcons
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.util.text.StringUtil
import training.ui.LearningUiManager
import training.util.replaceSpacesWithNonBreakSpace
import training.util.surroundWithNonBreakSpaces
import javax.swing.Icon
/* Here can be defined common methods for any DSL level */
interface LearningDslBase {
/** Show shortcut for [actionId] inside lesson step message */
fun action(actionId: String): String {
return "$actionId".surroundWithNonBreakSpaces()
}
/** Highlight as code inside lesson step message */
fun code(sourceSample: String): String {
return "${StringUtil.escapeXmlEntities(sourceSample).replaceSpacesWithNonBreakSpace()}
".surroundWithNonBreakSpaces()
}
/** Highlight some [text] */
fun strong(text: String): String {
return "${StringUtil.escapeXmlEntities(text)}"
}
/** Show an [icon] inside lesson step message */
fun icon(icon: Icon): String {
val index = LearningUiManager.getIconIndex(icon)
return "$index"
}
/** Show an icon from action widh [actionId] ID inside lesson step message */
fun actionIcon(actionId: String): String {
val icon = ActionManager.getInstance().getAction(actionId)?.templatePresentation?.icon ?: AllIcons.Toolbar.Unknown
val index = LearningUiManager.getIconIndex(icon)
return "$index"
}
fun shortcut(key: String): String {
return "${key}".surroundWithNonBreakSpaces()
}
}
PK Jmx training/dsl/LessonContext.kt// Copyright 2000-2021 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.
package training.dsl
import com.intellij.openapi.application.ModalityState
import com.intellij.util.concurrency.annotations.RequiresEdt
import org.intellij.lang.annotations.Language
import org.jetbrains.annotations.Nls
import training.learn.course.KLesson
@LearningDsl
abstract class LessonContext : LearningDslBase {
/**
* Start a new task in a lesson context
*/
@RequiresEdt
open fun task(taskContent: TaskContext.() -> Unit) = Unit
/**
* There will not be any freeze in GUI thread.
* The continuation of the script will be scheduled with the [delayMillis]
*/
open fun waitBeforeContinue(delayMillis: Int) = Unit
/// SHORTCUTS ///
fun prepareRuntimeTask(modalityState: ModalityState? = ModalityState.any(), preparation: TaskRuntimeContext.() -> Unit) {
task {
addFutureStep {
taskInvokeLater(modalityState) {
preparation()
completeStep()
}
}
}
}
/** Describe a simple task: just one action required */
fun actionTask(action: String, getText: TaskContext.(action: String) -> @Nls String) {
task {
text(getText(action))
trigger(action)
test { actions(action) }
}
}
fun text(@Language("HTML") @Nls text: String) = task { text(text) }
/**
* Just shortcut to write action name once
* @see task
*/
fun task(action: String, taskContent: TaskContext.(action: String) -> Unit) = task {
taskContent(action)
}
/** Select text in editor */
fun select(startLine: Int, startColumn: Int, endLine: Int, endColumn: Int) = prepareRuntimeTask {
select(startLine, startColumn, endLine, endColumn)
}
open fun caret(offset: Int) = prepareRuntimeTask {
caret(offset)
}
/** NOTE: [line] and [column] starts from 1 not from zero. So these parameters should be same as in editors. */
open fun caret(line: Int, column: Int) = prepareRuntimeTask {
caret(line, column)
}
open fun caret(text: String, select: Boolean = false) = prepareRuntimeTask {
caret(text, select)
}
open fun caret(position: LessonSamplePosition) = prepareRuntimeTask {
caret(position)
}
open fun prepareSample(sample: LessonSample, checkSdkConfiguration: Boolean = true) {
prepareRuntimeTask { setSample(sample) }
if (checkSdkConfiguration) {
sdkConfigurationTasks()
}
}
internal abstract val lesson: KLesson
}
PK YI| training/dsl/LessonSample.kt// Copyright 2000-2021 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.
package training.dsl
import com.intellij.openapi.editor.Editor
data class LessonSamplePosition(val id: Int, val startOffset: Int, val selection: Pair? = null)
class LessonSample(val text: String,
private val positions: Map) {
constructor(text: String, position: LessonSamplePosition) :
this(text, mapOf(Pair(0, LessonSamplePosition(0, position.startOffset, position.selection))))
constructor(text: String, startOffset: Int) : this(text, LessonSamplePosition(0, startOffset))
val startOffset: Int
get() = getPosition(0).startOffset
val selection: Pair?
get() = getPosition(0).selection
fun getPosition(id: Int): LessonSamplePosition {
return positions[id] ?: if (id == 0) LessonSamplePosition(0, 0) else error("No id $id")
}
fun insertAtPosition(id: Int, insert: String): LessonSample {
val position = getPosition(id)
val stringBuilder = StringBuilder(text)
stringBuilder.insert(position.startOffset, insert)
return parseLessonSample(stringBuilder.toString())
}
}
fun createFromTemplate(template: LessonSample, insert: String): LessonSample {
return LessonSample(LessonUtil.insertIntoSample(template, insert),
mapOf(Pair(0, LessonSamplePosition(0, template.startOffset, template.selection))))
}
fun parseLessonSample(rowText: String): LessonSample {
val text = if (rowText.isEmpty() || rowText.last() == '\n') rowText else rowText + '\n'
val positionList = mutableListOf()
val resultText = parseCarets(text, positionList)
val positions = mutableMapOf()
if (positionList.isNotEmpty()) {
for (position in positionList) {
if (positions[position.id] != null) error("several same id in the sample")
positions[position.id] = position
}
}
else {
positions[0] = LessonSamplePosition(id = 0, startOffset = 0)
}
return LessonSample(resultText, positions)
}
private fun parseCarets(text: String, positions: MutableList): String {
val result = StringBuilder()
var idx = 0
fun skipSpace() {
while (idx < text.length && text[idx] == ' ') idx++
}
fun equalSymbol(c: Char) = idx < text.length && text[idx] == c
fun acceptPrefix(prefix: String): Boolean {
if (text.subSequence(idx, text.length).startsWith(prefix)) {
idx += prefix.length
return true
}
return false
}
fun parseId(suffix: String): Int? {
skipSpace()
if (!acceptPrefix("id")) return null
skipSpace()
if (!acceptPrefix("=")) return null
skipSpace()
val end = text.indexOf(suffix, idx)
if (end == -1) return null
val id = Integer.parseInt(text.subSequence(idx, end).toString())
idx = end + suffix.length
return id
}
val selectionEnd = ""
var previousEndIdx = 0
while (idx >= 0 && idx < text.length) {
idx = text.indexOf('<', idx)
var startIdx = idx
fun appendText() {
result.append(text.subSequence(previousEndIdx, startIdx))
previousEndIdx = idx
}
fun parseSelection(): Int? {
appendText()
val startSelection = result.length
idx = text.indexOf(selectionEnd, idx)
if (idx == -1) return null
startIdx = idx
idx += selectionEnd.length
appendText()
return startSelection
}
if (idx < 0) break
idx++
if (acceptPrefix("caret")) {
if (acceptPrefix(">")) {
appendText()
positions.add(LessonSamplePosition(id = 0, startOffset = result.length))
}
else if (equalSymbol(' ')) {
val id = parseId("/>") ?: continue
appendText()
positions.add(LessonSamplePosition(id = id, startOffset = result.length))
}
}
else if (acceptPrefix("select")) {
if (acceptPrefix(">")) {
val startSelection = parseSelection() ?: continue
positions.add(LessonSamplePosition(id = 0, startOffset = result.length, selection = Pair(startSelection, result.length)))
}
else if (equalSymbol(' ')) {
val id = parseId(">") ?: continue
val startSelection = parseSelection() ?: continue
positions.add(LessonSamplePosition(id = id, startOffset = result.length, selection = Pair(startSelection, result.length)))
}
}
}
result.append(text.subSequence(previousEndIdx, text.length))
return result.toString()
}
fun prepareSampleFromCurrentState(editor: Editor): LessonSample {
val text = editor.document.text
val currentCaret = editor.caretModel.currentCaret
val position =
if (currentCaret.hasSelection()) LessonSamplePosition(id = 0, startOffset = currentCaret.offset,
selection = Pair(currentCaret.selectionStart, currentCaret.selectionEnd))
else LessonSamplePosition(id = 0, startOffset = currentCaret.offset)
return LessonSample(text, mapOf(Pair(0, position)))
}PK >s2 } training/dsl/LessonUtil.kt=rHjj('/-vV&55Ŗ1Ejy/&9:Dvh4@;y|;+oGy+<di7UE>eSVbhUC3z<y. 6fI