[kotlin] Translated onboarding tour to Kotlin, added a few more lessons

^KTIJ-26048 fixed


Merge-request: IJ-MR-120001
Merged-by: Frederik Haselmeier <Frederik.Haselmeier@jetbrains.com>

GitOrigin-RevId: 355d7d61360183b72a190c1d092e629b9774e0d9
This commit is contained in:
Frederik Haselmeier
2023-11-27 12:14:20 +00:00
committed by intellij-monorepo-bot
parent 9464787a8a
commit 30a5cc3323
28 changed files with 1269 additions and 655 deletions
@@ -1,14 +1,10 @@
// Copyright 2000-2019 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 com.intellij.java.ift.lesson.completion
import com.intellij.java.ift.JavaLessonsBundle
import training.dsl.*
import training.dsl.LessonUtil.restoreIfModifiedOrMoved
import training.learn.LessonsBundle
import training.learn.course.KLesson
class JavaSmartTypeCompletionLesson : KLesson("Smart type completion", LessonsBundle.message("smart.completion.lesson.name")) {
val sample: LessonSample = parseLessonSample("""
class JavaSmartTypeCompletionLesson : SmartTypeCompletionLessonBase() {
override val sample: LessonSample = parseLessonSample("""
import java.lang.String;
import java.util.HashSet;
import java.util.LinkedList;
@@ -34,43 +30,13 @@ class JavaSmartTypeCompletionLesson : KLesson("Smart type completion", LessonsBu
}
""".trimIndent())
override val lessonContent: LessonContext.() -> Unit = {
prepareSample(sample)
task {
text(JavaLessonsBundle.message("java.smart.type.completion.apply", action("SmartTypeCompletion"), action("EditorChooseLookupItem")))
trigger("SmartTypeCompletion")
stateCheck {
val text = editor.document.text
text.contains("strings = arrayBlockingQueue;") || text.contains("strings = linkedList;")
}
restoreIfModifiedOrMoved(sample)
testSmartCompletion()
}
override val firstCompletionItem: String = "linkedList"
override val firstCompletionCheck: String = "strings = linkedList;"
override val secondCompletionItem: String = "arrayBlockingQueue"
override val secondCompletionCheck: String = "return arrayBlockingQueue.toArray(new String[0]);"
override fun LessonContext.setCaretForSecondItem() {
caret(20, 16)
task {
text(JavaLessonsBundle.message("java.smart.type.completion.return", action("SmartTypeCompletion"), action("EditorChooseLookupItem")))
trigger("SmartTypeCompletion")
stateCheck {
val text = editor.document.text
text.contains("return arrayBlockingQueue.toArray(new String[0]);")
|| text.contains("return strings.toArray(new String[0]);")
}
restoreIfModifiedOrMoved()
testSmartCompletion()
}
}
private fun TaskContext.testSmartCompletion() {
test {
invokeActionViaShortcut("CTRL SHIFT SPACE")
ideFrame {
jListContains("arrayBlockingQueue").item(0).doubleClick()
}
}
}
override val helpLinks: Map<String, String> get() = mapOf(
Pair(LessonsBundle.message("help.code.completion"),
LessonUtil.getHelpLink("auto-completing-code.html")),
)
}
@@ -0,0 +1,69 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.ift.lesson.completion
import com.intellij.java.ift.JavaLessonsBundle
import training.dsl.LessonContext
import training.dsl.LessonSample
import training.dsl.LessonUtil
import training.dsl.LessonUtil.restoreIfModifiedOrMoved
import training.dsl.TaskContext
import training.learn.LessonsBundle
import training.learn.course.KLesson
import training.util.isToStringContains
abstract class SmartTypeCompletionLessonBase : KLesson("Smart type completion", LessonsBundle.message("smart.completion.lesson.name")) {
abstract val sample: LessonSample
abstract val firstCompletionItem: String
abstract val firstCompletionCheck: String
abstract val secondCompletionItem: String
abstract val secondCompletionCheck: String
abstract fun LessonContext.setCaretForSecondItem()
override val lessonContent: LessonContext.() -> Unit = {
prepareSample(sample)
task {
text(JavaLessonsBundle.message("java.smart.type.completion.apply", action("SmartTypeCompletion"), action("EditorChooseLookupItem")))
trigger("SmartTypeCompletion")
triggerAndBorderHighlight().listItem {
it.isToStringContains(firstCompletionItem)
}
stateCheck {
val text = editor.document.text
text.contains(firstCompletionCheck)
}
restoreIfModifiedOrMoved(sample)
testSmartCompletion(firstCompletionItem)
}
setCaretForSecondItem()
task {
text(JavaLessonsBundle.message("java.smart.type.completion.return", action("SmartTypeCompletion"), action("EditorChooseLookupItem")))
trigger("SmartTypeCompletion")
triggerAndBorderHighlight().listItem {
it.isToStringContains(secondCompletionItem)
}
stateCheck {
val text = editor.document.text
text.contains(secondCompletionCheck)
}
restoreIfModifiedOrMoved()
testSmartCompletion(secondCompletionItem)
}
}
private fun TaskContext.testSmartCompletion(item: String) {
test {
invokeActionViaShortcut("CTRL SHIFT SPACE")
ideFrame {
jListContains(item).item(0).doubleClick()
}
}
}
override val helpLinks: Map<String, String> get() = mapOf(
Pair(LessonsBundle.message("help.code.completion"),
LessonUtil.getHelpLink("auto-completing-code.html")),
)
}
@@ -2,113 +2,22 @@
package com.intellij.java.ift.lesson.essential
import com.intellij.codeInsight.daemon.impl.analysis.HighlightingFeature
import com.intellij.execution.RunManager
import com.intellij.execution.ui.UIExperiment
import com.intellij.icons.AllIcons
import com.intellij.ide.DataManager
import com.intellij.ide.actions.searcheverywhere.SearchEverywhereManagerImpl
import com.intellij.ide.actions.searcheverywhere.SearchEverywhereUI
import com.intellij.ide.ui.UISettings
import com.intellij.ide.util.PropertiesComponent
import com.intellij.ide.util.gotoByName.GotoActionModel
import com.intellij.idea.ActionsBundle
import com.intellij.java.ift.JavaLessonsBundle
import com.intellij.java.ift.JavaProjectUtil
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.ex.ActionUtil
import com.intellij.openapi.actionSystem.impl.ActionButton
import com.intellij.openapi.application.invokeLater
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.editor.actions.ToggleCaseAction
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.module.LanguageLevelUtil
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.progress.runBackgroundableTask
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectBundle
import com.intellij.openapi.projectRoots.JavaSdk
import com.intellij.openapi.projectRoots.SdkType
import com.intellij.openapi.roots.ui.configuration.SdkDetector
import com.intellij.openapi.ui.MessageDialogBuilder
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.ui.ex.MultiLineLabel
import com.intellij.openapi.ui.popup.Balloon
import com.intellij.openapi.util.NlsSafe
import com.intellij.openapi.util.WindowStateService
import com.intellij.openapi.wm.ToolWindowManager
import com.intellij.openapi.wm.impl.FocusManagerImpl
import com.intellij.ui.IdeUICustomization
import com.intellij.ui.UIBundle
import com.intellij.ui.components.fields.ExtendableTextField
import com.intellij.ui.components.panels.NonOpaquePanel
import com.intellij.ui.dsl.builder.Panel
import com.intellij.ui.tree.TreeVisitor
import com.intellij.util.PlatformUtils
import com.intellij.util.ui.UIUtil
import com.intellij.util.ui.tree.TreeUtil
import com.intellij.xdebugger.XDebuggerManager
import com.siyeh.InspectionGadgetsBundle
import com.siyeh.IntentionPowerPackBundle
import kotlinx.serialization.json.JsonObjectBuilder
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.put
import org.jetbrains.annotations.Nls
import training.FeaturesTrainerIcons
import training.dsl.*
import training.dsl.LessonUtil.adjustSearchEverywherePosition
import training.dsl.LessonUtil.checkEditorModification
import training.dsl.LessonUtil.restoreIfModified
import training.dsl.LessonUtil.restoreIfModifiedOrMoved
import training.dsl.LessonUtil.restorePopupPosition
import training.learn.LearnBundle
import training.learn.LessonsBundle
import training.learn.course.KLesson
import training.learn.course.LessonProperties
import training.learn.lesson.LessonManager
import training.learn.lesson.general.run.clearBreakpoints
import training.learn.lesson.general.run.toggleBreakpointTask
import training.project.ProjectUtils
import training.ui.LearningUiHighlightingManager
import training.ui.LearningUiManager
import training.ui.getFeedbackProposedPropertyName
import training.util.*
import java.awt.Point
import java.awt.event.KeyEvent
import java.util.concurrent.CompletableFuture
import javax.swing.JTree
import javax.swing.JWindow
import javax.swing.tree.TreePath
class JavaOnboardingTourLesson : KLesson("java.onboarding", JavaLessonsBundle.message("java.onboarding.lesson.name")) {
private lateinit var openLearnTaskId: TaskContext.TaskId
private var useDelay: Boolean = false
private val demoFileDirectory: String = "src"
private val demoFileNameWithoutExtension: String = "Welcome"
private val demoFileName: String = "$demoFileNameWithoutExtension.java"
private val uiSettings get() = UISettings.getInstance()
override val properties: LessonProperties = LessonProperties(
canStartInDumbMode = true,
openFileAtStart = false
)
override val testScriptProperties: TaskTestContext.TestScriptProperties = TaskTestContext.TestScriptProperties(skipTesting = true)
private var backupPopupLocation: Point? = null
private var hideToolStripesPreference = false
private var showNavigationBarPreference = true
@NlsSafe
private var jdkAtStart: String = "undefined"
val sample: LessonSample = parseLessonSample("""
class JavaOnboardingTourLesson : OnboardingTourLessonBase("java.onboarding") {
override val demoFileExtension: String = "java"
override val learningProjectName: String = "IdeaLearningProject"
override val sample: LessonSample = parseLessonSample("""
import java.util.Arrays;
import java.util.List;
@@ -128,459 +37,9 @@ class JavaOnboardingTourLesson : KLesson("java.onboarding", JavaLessonsBundle.me
}
""".trimIndent())
override val lessonContent: LessonContext.() -> Unit = {
prepareRuntimeTask {
jdkAtStart = getCurrentJdkVersionString(project)
useDelay = true
invokeActionForFocusContext(getActionById("Stop"))
val runManager = RunManager.getInstance(project)
runManager.allSettings.forEach(runManager::removeConfiguration)
override val completionStepExpectedCompletion: String = "length"
val root = ProjectUtils.getCurrentLearningProjectRoot()
val srcDir = root.findChild(demoFileDirectory) ?: error("'src' directory not found.")
if (srcDir.findChild(demoFileName) == null) invokeLater {
runWriteAction {
srcDir.createChildData(this, demoFileName)
// todo: This file shows with .java extension in the Project view and this extension disappears when user open it
// (because we fill the file after the user open it) Fill the file immediately in this place?
}
}
}
clearBreakpoints()
checkUiSettings()
projectTasks()
prepareSample(sample, checkSdkConfiguration = false)
openLearnToolwindow()
sdkConfigurationTasks()
waitIndexingTasks()
runTasks()
debugTasks()
completionSteps()
waitBeforeContinue(500)
contextActions()
waitBeforeContinue(500)
searchEverywhereTasks()
task {
text(JavaLessonsBundle.message("java.onboarding.epilog",
getCallBackActionId("CloseProject"),
LessonUtil.returnToWelcomeScreenRemark(),
LearningUiManager.addCallback { LearningUiManager.resetModulesView() }))
}
}
override fun onLessonEnd(project: Project, lessonEndInfo: LessonEndInfo) {
prepareFeedbackData(project, lessonEndInfo)
restorePopupPosition(project, SearchEverywhereManagerImpl.LOCATION_SETTINGS_KEY, backupPopupLocation)
backupPopupLocation = null
uiSettings.hideToolStripes = hideToolStripesPreference
uiSettings.showNavigationBar = showNavigationBarPreference
uiSettings.fireUISettingsChanged()
if (!lessonEndInfo.lessonPassed) {
LessonUtil.showFeedbackNotification(this, project)
return
}
val dataContextPromise = DataManager.getInstance().dataContextFromFocusAsync
invokeLater {
val result = MessageDialogBuilder.yesNoCancel(JavaLessonsBundle.message("java.onboarding.finish.title"),
JavaLessonsBundle.message("java.onboarding.finish.text",
LessonUtil.returnToWelcomeScreenRemark()))
.yesText(JavaLessonsBundle.message("java.onboarding.finish.exit"))
.noText(JavaLessonsBundle.message("java.onboarding.finish.modules"))
.icon(FeaturesTrainerIcons.PluginIcon)
.show(project)
when (result) {
Messages.YES -> invokeLater {
LessonManager.instance.stopLesson()
val closeAction = getActionById("CloseProject")
dataContextPromise.onSuccess { context ->
invokeLater {
val event = AnActionEvent.createFromAnAction(closeAction, null, ActionPlaces.LEARN_TOOLWINDOW, context)
ActionUtil.performActionDumbAwareWithCallbacks(closeAction, event)
}
}
}
Messages.NO -> invokeLater {
LearningUiManager.resetModulesView()
}
}
if (result != Messages.YES) {
LessonUtil.showFeedbackNotification(this, project)
}
}
}
private fun prepareFeedbackData(project: Project, lessonEndInfo: LessonEndInfo) {
val primaryLanguage = module.primaryLanguage
if (primaryLanguage == null) {
thisLogger().error("Onboarding lesson has no language support for some magical reason")
return
}
val configPropertyName = getFeedbackProposedPropertyName(primaryLanguage)
if (PropertiesComponent.getInstance().getBoolean(configPropertyName, false)) {
return
}
val jdkVersionsFuture = CompletableFuture<List<String>>()
runBackgroundableTask(ProjectBundle.message("progress.title.detecting.sdks"), project, false) { indicator ->
val jdkVersions = mutableListOf<String>()
SdkDetector.getInstance().detectSdks(JavaSdk.getInstance(), indicator, object : SdkDetector.DetectedSdkListener {
override fun onSdkDetected(type: SdkType, version: String, home: String) {
jdkVersions.add(version)
}
override fun onSearchCompleted() {
jdkVersionsFuture.complete(jdkVersions)
}
})
}
@Suppress("HardCodedStringLiteral")
val currentJdkVersion: @NlsSafe String = getCurrentJdkVersionString(project)
val module = ModuleManager.getInstance(project).modules.first()
@Suppress("HardCodedStringLiteral")
val currentLanguageLevel: @NlsSafe String = LanguageLevelUtil.getEffectiveLanguageLevel(module).name
primaryLanguage.onboardingFeedbackData = object : OnboardingFeedbackData("IDEA Onboarding Tour Feedback", lessonEndInfo) {
override val feedbackReportId = "idea_onboarding_tour"
override val additionalFeedbackFormatVersion: Int = 1
private val jdkVersions: List<String>? by lazy {
if (jdkVersionsFuture.isDone) jdkVersionsFuture.get() else null
}
override val addAdditionalSystemData: JsonObjectBuilder.() -> Unit = {
put("jdk_at_start", jdkAtStart)
put("current_jdk", currentJdkVersion)
put("language_level", currentLanguageLevel)
put("found_jdk", buildJsonArray {
for (version in jdkVersions ?: emptyList()) {
add(JsonPrimitive(version))
}
})
}
override val addRowsForUserAgreement: Panel.() -> Unit = {
row(JavaLessonsBundle.message("java.onboarding.feedback.system.found.jdks")) {
val versions: @NlsSafe String = jdkVersions?.joinToString("\n") ?: "none"
cell(MultiLineLabel(versions))
}
row(JavaLessonsBundle.message("java.onboarding.feedback.system.jdk.at.start")) {
label(jdkAtStart)
}
row(JavaLessonsBundle.message("java.onboarding.feedback.system.current.jdk")) {
label(currentJdkVersion)
}
row(JavaLessonsBundle.message("java.onboarding.feedback.system.lang.level")) {
label(currentLanguageLevel)
}
}
override fun feedbackHasBeenProposed() {
PropertiesComponent.getInstance().setValue(configPropertyName, true, false)
}
}
}
private fun getCurrentJdkVersionString(project: Project): String {
return JavaProjectUtil.getEffectiveJdk(project)?.let { JavaSdk.getInstance().getVersionString(it) } ?: "none"
}
private fun LessonContext.debugTasks() {
clearBreakpoints()
var logicalPosition = LogicalPosition(0, 0)
prepareRuntimeTask {
logicalPosition = editor.offsetToLogicalPosition(sample.startOffset)
}
caret(sample.startOffset)
toggleBreakpointTask(sample, { logicalPosition }, checkLine = false) {
text(JavaLessonsBundle.message("java.onboarding.balloon.click.here"),
LearningBalloonConfig(Balloon.Position.below, width = 0, cornerToPointerDistance = 20))
text(JavaLessonsBundle.message("java.onboarding.toggle.breakpoint.1",
code("6.5"), code("findAverage"), code("26")))
text(JavaLessonsBundle.message("java.onboarding.toggle.breakpoint.2"))
}
highlightButtonById("Debug", highlightInside = false, usePulsation = false)
actionTask("Debug") {
showBalloonOnHighlightingComponent(JavaLessonsBundle.message("java.onboarding.balloon.start.debugging"))
restoreState {
lineWithBreakpoints() != setOf(logicalPosition.line)
}
restoreIfModified(sample)
JavaLessonsBundle.message("java.onboarding.start.debugging", icon(AllIcons.Actions.StartDebugger))
}
lateinit var debuggerGotItTaskId: TaskContext.TaskId
task {
debuggerGotItTaskId = taskId
}
highlightDebugActionsToolbar()
task {
rehighlightPreviousUi = true
gotItStep(Balloon.Position.above, width = 0,
JavaLessonsBundle.message("java.onboarding.balloon.about.debug.panel",
strong(UIBundle.message("tool.window.name.debug")),
strong(LessonsBundle.message("debug.workflow.lesson.name"))))
restoreByUi(debuggerGotItTaskId)
}
highlightButtonById("Stop", highlightInside = false, usePulsation = false)
task {
val position = if (UIExperiment.isNewDebuggerUIEnabled()) Balloon.Position.above else Balloon.Position.atRight
showBalloonOnHighlightingComponent(JavaLessonsBundle.message("java.onboarding.balloon.stop.debugging"),
position) { list -> list.maxByOrNull { it.locationOnScreen.y } }
text(JavaLessonsBundle.message("java.onboarding.stop.debugging", icon(AllIcons.Actions.Suspend)))
restoreIfModified(sample)
stateCheck {
XDebuggerManager.getInstance(project).currentSession == null
}
}
prepareRuntimeTask {
LearningUiHighlightingManager.clearHighlights()
}
}
private fun LessonContext.waitIndexingTasks() {
task {
triggerAndBorderHighlight().component { progress: NonOpaquePanel ->
progress.javaClass.name.contains("InlineProgressPanel")
}
}
task {
text(JavaLessonsBundle.message("java.onboarding.indexing.description"))
text(JavaLessonsBundle.message("java.onboarding.wait.indexing"), LearningBalloonConfig(Balloon.Position.above, 0))
waitSmartModeStep()
}
waitBeforeContinue(300)
prepareRuntimeTask {
LearningUiHighlightingManager.clearHighlights()
}
}
private fun LessonContext.runTasks() {
highlightRunToolbar(highlightInside = false, usePulsation = false)
task {
triggerUI {
clearPreviousHighlights = false
}.component { ui: ActionButton -> ActionManager.getInstance().getId(ui.action) == "Run" }
}
task {
val introductionText = JavaLessonsBundle.message("java.onboarding.temporary.configuration.description",
strong(ActionsBundle.actionText("NewUiRunWidget")),
icon(AllIcons.Actions.Execute),
icon(AllIcons.Actions.StartDebugger))
val runOptionsText = if (PlatformUtils.isIdeaUltimate()) {
JavaLessonsBundle.message("java.onboarding.run.options.ultimate",
icon(AllIcons.Actions.Profile),
icon(AllIcons.General.RunWithCoverage),
icon(AllIcons.Actions.More))
}
else {
JavaLessonsBundle.message("java.onboarding.run.options.community",
icon(AllIcons.General.RunWithCoverage),
icon(AllIcons.Actions.More))
}
text("$introductionText $runOptionsText")
text(JavaLessonsBundle.message("java.onboarding.run.sample", icon(AllIcons.Actions.Execute), action("Run")))
text(JavaLessonsBundle.message("java.onboarding.run.sample.balloon", icon(AllIcons.Actions.Execute), action("Run")),
LearningBalloonConfig(Balloon.Position.below, 0))
checkToolWindowState("Run", true)
restoreIfModified(sample)
}
}
private fun LessonContext.openLearnToolwindow() {
task {
triggerAndBorderHighlight().component { stripe: ActionButton ->
stripe.action.templateText == LearnBundle.message("toolwindow.stripe.Learn")
}
}
task {
openLearnTaskId = taskId
text(JavaLessonsBundle.message("java.onboarding.balloon.open.learn.toolbar", strong(LearnBundle.message("toolwindow.stripe.Learn"))),
LearningBalloonConfig(Balloon.Position.atRight, width = 0, duplicateMessage = true))
stateCheck {
ToolWindowManager.getInstance(project).getToolWindow("Learn")?.isVisible == true
}
restoreIfModified(sample)
}
prepareRuntimeTask {
LearningUiHighlightingManager.clearHighlights()
requestEditorFocus()
}
}
private fun LessonContext.checkUiSettings() {
hideToolStripesPreference = uiSettings.hideToolStripes
showNavigationBarPreference = uiSettings.showNavigationBar
showInvalidDebugLayoutWarning()
if (!hideToolStripesPreference && (showNavigationBarPreference || uiSettings.showMainToolbar)) {
// a small hack to have same tasks count. It is needed to track statistics result.
task { }
task { }
return
}
task {
text(JavaLessonsBundle.message("java.onboarding.change.ui.settings"))
proceedLink()
}
prepareRuntimeTask {
uiSettings.hideToolStripes = false
uiSettings.showNavigationBar = true
uiSettings.fireUISettingsChanged()
}
}
private fun LessonContext.projectTasks() {
prepareRuntimeTask {
LessonUtil.hideStandardToolwindows(project)
}
task {
triggerAndBorderHighlight().component { stripe: ActionButton ->
stripe.action.templateText == IdeUICustomization.getInstance().getProjectViewTitle(project)
}
}
lateinit var openProjectViewTask: TaskContext.TaskId
task {
openProjectViewTask = taskId
var projectDirExpanded = false
text(JavaLessonsBundle.message("java.onboarding.project.view.description",
action("ActivateProjectToolWindow")))
text(JavaLessonsBundle.message("java.onboarding.balloon.project.view"),
LearningBalloonConfig(Balloon.Position.atRight, width = 0, cornerToPointerDistance = 8))
triggerUI().treeItem { tree: JTree, path: TreePath ->
val result = path.pathCount >= 2 && path.getPathComponent(1).isToStringContains("IdeaLearningProject")
if (result) {
if (!projectDirExpanded) {
invokeLater { tree.expandPath(path) }
}
projectDirExpanded = true
}
result
}
}
task {
var srcDirCollapsed = false
triggerAndBorderHighlight().treeItem { tree: JTree, path: TreePath ->
val result = path.pathCount >= 3
&& path.getPathComponent(1).isToStringContains("IdeaLearningProject")
&& path.getPathComponent(2).isToStringContains(demoFileDirectory)
if (result) {
if (!srcDirCollapsed) {
invokeLater { tree.collapsePath(path) }
}
srcDirCollapsed = true
}
result
}
}
fun isDemoFilePath(path: TreePath) =
path.pathCount >= 4 && path.getPathComponent(3).isToStringContains(demoFileNameWithoutExtension)
task {
text(JavaLessonsBundle.message("java.onboarding.balloon.source.directory", strong(demoFileDirectory)),
LearningBalloonConfig(Balloon.Position.atRight, duplicateMessage = true, width = 0))
triggerAndBorderHighlight().treeItem { _: JTree, path: TreePath ->
isDemoFilePath(path)
}
restoreByUi(openProjectViewTask)
}
task {
text(JavaLessonsBundle.message("java.onboarding.balloon.open.file", strong(demoFileName)),
LearningBalloonConfig(Balloon.Position.atRight, duplicateMessage = true, width = 0))
stateCheck l@{
if (FileEditorManager.getInstance(project).selectedTextEditor == null) return@l false
virtualFile.name == demoFileName
}
restoreState {
(previous.ui as? JTree)?.takeIf { tree ->
TreeUtil.visitVisibleRows(tree, TreeVisitor { path ->
if (isDemoFilePath(path)) TreeVisitor.Action.INTERRUPT else TreeVisitor.Action.CONTINUE
}) != null
}?.isShowing?.not() ?: true
}
}
}
private fun LessonContext.completionSteps() {
prepareRuntimeTask {
setSample(sample.insertAtPosition(2, " / values<caret>"))
FocusManagerImpl.getInstance(project).requestFocusInProject(editor.contentComponent, project)
}
task {
val textToFind = "result / values"
triggerOnEditorText(textToFind, centerOffset = textToFind.length)
}
task {
text(JavaLessonsBundle.message("java.onboarding.type.division",
code(" / values")))
text(JavaLessonsBundle.message("java.onboarding.invoke.completion", code(".")))
text(JavaLessonsBundle.message("java.onboarding.invoke.completion.balloon", code(".")),
LearningBalloonConfig(Balloon.Position.below, width = 0))
triggerAndBorderHighlight().listItem { // no highlighting
it.isToStringContains("length")
}
proposeRestoreForInvalidText(".")
}
task {
text(JavaLessonsBundle.message("java.onboarding.choose.values.item",
code("length"), action("EditorChooseLookupItem")))
text(JavaLessonsBundle.message("java.onboarding.invoke.completion.tip", action("CodeCompletion")))
stateCheck {
checkEditorModification(sample, modificationPositionId = 2, needChange = "/values.length")
}
}
}
private fun LessonContext.contextActions() {
override fun LessonContext.contextActions() {
val quickFixMessage = InspectionGadgetsBundle.message("foreach.replace.quickfix")
caret(sample.getPosition(3))
@@ -643,50 +102,4 @@ class JavaOnboardingTourLesson : KLesson("java.onboarding", JavaLessonsBundle.me
restoreByUi(delayMillis = defaultRestoreDelay)
}
}
private fun LessonContext.searchEverywhereTasks() {
val toggleCase = ActionsBundle.message("action.EditorToggleCase.text")
caret("AVERAGE", select = true)
task("SearchEverywhere") {
text(JavaLessonsBundle.message("java.onboarding.invoke.search.everywhere.1",
strong(toggleCase), code("AVERAGE")))
text(JavaLessonsBundle.message("java.onboarding.invoke.search.everywhere.2",
LessonUtil.rawKeyStroke(KeyEvent.VK_SHIFT), LessonUtil.actionName(it)))
triggerAndBorderHighlight().component { ui: ExtendableTextField ->
UIUtil.getParentOfType(SearchEverywhereUI::class.java, ui) != null
}
restoreIfModifiedOrMoved()
}
task {
transparentRestore = true
before {
if (backupPopupLocation != null) return@before
val ui = previous.ui ?: return@before
val popupWindow = UIUtil.getParentOfType(JWindow::class.java, ui) ?: return@before
val oldPopupLocation = WindowStateService.getInstance(project).getLocation(SearchEverywhereManagerImpl.LOCATION_SETTINGS_KEY)
if (adjustSearchEverywherePosition(popupWindow, "of array ") || LessonUtil.adjustPopupPosition(project, popupWindow)) {
backupPopupLocation = oldPopupLocation
}
}
text(JavaLessonsBundle.message("java.onboarding.search.everywhere.description",
code("AVERAGE"), code(JavaLessonsBundle.message("toggle.case.part"))))
triggerAndBorderHighlight().listItem { item ->
val value = (item as? GotoActionModel.MatchedValue)?.value
(value as? GotoActionModel.ActionWrapper)?.action is ToggleCaseAction
}
restoreByUi()
restoreIfModifiedOrMoved()
}
task {
text(JavaLessonsBundle.message("java.onboarding.apply.action", strong(toggleCase), LessonUtil.rawEnter()))
stateCheck {
editor.document.text.contains("\"average")
}
restoreByUi(delayMillis = defaultRestoreDelay)
}
text(JavaLessonsBundle.message("java.onboarding.case.changed"))
}
}
@@ -0,0 +1,615 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.ift.lesson.essential
import com.intellij.execution.RunManager
import com.intellij.execution.ui.UIExperiment
import com.intellij.icons.AllIcons
import com.intellij.ide.DataManager
import com.intellij.ide.actions.searcheverywhere.SearchEverywhereManagerImpl
import com.intellij.ide.actions.searcheverywhere.SearchEverywhereUI
import com.intellij.ide.ui.UISettings
import com.intellij.ide.util.PropertiesComponent
import com.intellij.ide.util.gotoByName.GotoActionModel
import com.intellij.idea.ActionsBundle
import com.intellij.java.ift.JavaLessonsBundle
import com.intellij.java.ift.JavaProjectUtil
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.ex.ActionUtil
import com.intellij.openapi.actionSystem.impl.ActionButton
import com.intellij.openapi.application.invokeLater
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.editor.actions.ToggleCaseAction
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.module.LanguageLevelUtil
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.progress.runBackgroundableTask
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectBundle
import com.intellij.openapi.projectRoots.JavaSdk
import com.intellij.openapi.projectRoots.SdkType
import com.intellij.openapi.roots.ui.configuration.SdkDetector
import com.intellij.openapi.ui.MessageDialogBuilder
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.ui.ex.MultiLineLabel
import com.intellij.openapi.ui.popup.Balloon
import com.intellij.openapi.util.NlsSafe
import com.intellij.openapi.util.WindowStateService
import com.intellij.openapi.wm.ToolWindowManager
import com.intellij.openapi.wm.impl.FocusManagerImpl
import com.intellij.ui.IdeUICustomization
import com.intellij.ui.UIBundle
import com.intellij.ui.components.fields.ExtendableTextField
import com.intellij.ui.components.panels.NonOpaquePanel
import com.intellij.ui.dsl.builder.Panel
import com.intellij.ui.tree.TreeVisitor
import com.intellij.util.PlatformUtils
import com.intellij.util.ui.UIUtil
import com.intellij.util.ui.tree.TreeUtil
import com.intellij.xdebugger.XDebuggerManager
import kotlinx.serialization.json.JsonObjectBuilder
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.put
import training.FeaturesTrainerIcons
import training.dsl.*
import training.dsl.LessonUtil.adjustSearchEverywherePosition
import training.dsl.LessonUtil.checkEditorModification
import training.dsl.LessonUtil.restoreIfModified
import training.dsl.LessonUtil.restoreIfModifiedOrMoved
import training.dsl.LessonUtil.restorePopupPosition
import training.learn.LearnBundle
import training.learn.LessonsBundle
import training.learn.course.KLesson
import training.learn.course.LessonProperties
import training.learn.lesson.LessonManager
import training.learn.lesson.general.run.clearBreakpoints
import training.learn.lesson.general.run.toggleBreakpointTask
import training.project.ProjectUtils
import training.ui.LearningUiHighlightingManager
import training.ui.LearningUiManager
import training.ui.getFeedbackProposedPropertyName
import training.util.*
import java.awt.Point
import java.awt.event.KeyEvent
import java.util.concurrent.CompletableFuture
import javax.swing.JTree
import javax.swing.JWindow
import javax.swing.tree.TreePath
abstract class OnboardingTourLessonBase(id: String) : KLesson(id, JavaLessonsBundle.message("java.onboarding.lesson.name")) {
private lateinit var openLearnTaskId: TaskContext.TaskId
private var useDelay: Boolean = false
private val demoFileDirectory: String = "src"
private val demoFileNameWithoutExtension: String = "Welcome"
abstract val demoFileExtension: String
private val demoFileName: String
get() = "$demoFileNameWithoutExtension.$demoFileExtension"
private val uiSettings get() = UISettings.getInstance()
override val properties: LessonProperties = LessonProperties(
canStartInDumbMode = true,
openFileAtStart = false
)
override val testScriptProperties: TaskTestContext.TestScriptProperties = TaskTestContext.TestScriptProperties(skipTesting = true)
private var backupPopupLocation: Point? = null
private var hideToolStripesPreference = false
private var showNavigationBarPreference = true
@NlsSafe
private var jdkAtStart: String = "undefined"
abstract val sample: LessonSample
abstract val learningProjectName: String
override val lessonContent: LessonContext.() -> Unit = {
prepareRuntimeTask {
jdkAtStart = getCurrentJdkVersionString(project)
useDelay = true
invokeActionForFocusContext(getActionById("Stop"))
val runManager = RunManager.getInstance(project)
runManager.allSettings.forEach(runManager::removeConfiguration)
val root = ProjectUtils.getCurrentLearningProjectRoot()
val srcDir = root.findChild(demoFileDirectory) ?: error("'src' directory not found.")
if (srcDir.findChild(demoFileName) == null) invokeLater {
runWriteAction {
srcDir.createChildData(this, demoFileName)
// todo: This file shows with .java extension in the Project view and this extension disappears when user open it
// (because we fill the file after the user open it) Fill the file immediately in this place?
}
}
}
clearBreakpoints()
checkUiSettings()
projectTasks()
prepareSample(sample, checkSdkConfiguration = false)
openLearnToolwindow()
sdkConfigurationTasks()
waitIndexingTasks()
runTasks()
debugTasks()
completionSteps()
waitBeforeContinue(500)
contextActions()
waitBeforeContinue(500)
searchEverywhereTasks()
task {
text(JavaLessonsBundle.message("java.onboarding.epilog",
getCallBackActionId("CloseProject"),
LessonUtil.returnToWelcomeScreenRemark(),
LearningUiManager.addCallback { LearningUiManager.resetModulesView() }))
}
}
override fun onLessonEnd(project: Project, lessonEndInfo: LessonEndInfo) {
prepareFeedbackData(project, lessonEndInfo)
restorePopupPosition(project, SearchEverywhereManagerImpl.LOCATION_SETTINGS_KEY, backupPopupLocation)
backupPopupLocation = null
uiSettings.hideToolStripes = hideToolStripesPreference
uiSettings.showNavigationBar = showNavigationBarPreference
uiSettings.fireUISettingsChanged()
if (!lessonEndInfo.lessonPassed) {
LessonUtil.showFeedbackNotification(this, project)
return
}
val dataContextPromise = DataManager.getInstance().dataContextFromFocusAsync
invokeLater {
val result = MessageDialogBuilder.yesNoCancel(JavaLessonsBundle.message("java.onboarding.finish.title"),
JavaLessonsBundle.message("java.onboarding.finish.text",
LessonUtil.returnToWelcomeScreenRemark()))
.yesText(JavaLessonsBundle.message("java.onboarding.finish.exit"))
.noText(JavaLessonsBundle.message("java.onboarding.finish.modules"))
.icon(FeaturesTrainerIcons.PluginIcon)
.show(project)
when (result) {
Messages.YES -> invokeLater {
LessonManager.instance.stopLesson()
val closeAction = getActionById("CloseProject")
dataContextPromise.onSuccess { context ->
invokeLater {
val event = AnActionEvent.createFromAnAction(closeAction, null, ActionPlaces.LEARN_TOOLWINDOW, context)
ActionUtil.performActionDumbAwareWithCallbacks(closeAction, event)
}
}
}
Messages.NO -> invokeLater {
LearningUiManager.resetModulesView()
}
}
if (result != Messages.YES) {
LessonUtil.showFeedbackNotification(this, project)
}
}
}
private fun getCurrentJdkVersionString(project: Project): String {
return JavaProjectUtil.getEffectiveJdk(project)?.let { JavaSdk.getInstance().getVersionString(it) } ?: "none"
}
private fun prepareFeedbackData(project: Project, lessonEndInfo: LessonEndInfo) {
val primaryLanguage = module.primaryLanguage
if (primaryLanguage == null) {
thisLogger().error("Onboarding lesson has no language support for some magical reason")
return
}
val configPropertyName = getFeedbackProposedPropertyName(primaryLanguage)
if (PropertiesComponent.getInstance().getBoolean(configPropertyName, false)) {
return
}
val jdkVersionsFuture = CompletableFuture<List<String>>()
runBackgroundableTask(ProjectBundle.message("progress.title.detecting.sdks"), project, false) { indicator ->
val jdkVersions = mutableListOf<String>()
SdkDetector.getInstance().detectSdks(JavaSdk.getInstance(), indicator, object : SdkDetector.DetectedSdkListener {
override fun onSdkDetected(type: SdkType, version: String, home: String) {
jdkVersions.add(version)
}
override fun onSearchCompleted() {
jdkVersionsFuture.complete(jdkVersions)
}
})
}
@Suppress("HardCodedStringLiteral")
val currentJdkVersion: @NlsSafe String = getCurrentJdkVersionString(project)
val module = ModuleManager.getInstance(project).modules.first()
@Suppress("HardCodedStringLiteral")
val currentLanguageLevel: @NlsSafe String = LanguageLevelUtil.getEffectiveLanguageLevel(module).name
primaryLanguage.onboardingFeedbackData = object : OnboardingFeedbackData("IDEA Onboarding Tour Feedback", lessonEndInfo) {
override val feedbackReportId = "idea_onboarding_tour"
override val additionalFeedbackFormatVersion: Int = 1
private val jdkVersions: List<String>? by lazy {
if (jdkVersionsFuture.isDone) jdkVersionsFuture.get() else null
}
override val addAdditionalSystemData: JsonObjectBuilder.() -> Unit = {
put("jdk_at_start", jdkAtStart)
put("current_jdk", currentJdkVersion)
put("language_level", currentLanguageLevel)
put("found_jdk", buildJsonArray {
for (version in jdkVersions ?: emptyList()) {
add(JsonPrimitive(version))
}
})
}
override val addRowsForUserAgreement: Panel.() -> Unit = {
row(JavaLessonsBundle.message("java.onboarding.feedback.system.found.jdks")) {
val versions: @NlsSafe String = jdkVersions?.joinToString("\n") ?: "none"
cell(MultiLineLabel(versions))
}
row(JavaLessonsBundle.message("java.onboarding.feedback.system.jdk.at.start")) {
label(jdkAtStart)
}
row(JavaLessonsBundle.message("java.onboarding.feedback.system.current.jdk")) {
label(currentJdkVersion)
}
row(JavaLessonsBundle.message("java.onboarding.feedback.system.lang.level")) {
label(currentLanguageLevel)
}
}
override fun feedbackHasBeenProposed() {
PropertiesComponent.getInstance().setValue(configPropertyName, true, false)
}
}
}
private fun LessonContext.debugTasks() {
clearBreakpoints()
var logicalPosition = LogicalPosition(0, 0)
prepareRuntimeTask {
logicalPosition = editor.offsetToLogicalPosition(sample.startOffset)
}
caret(sample.startOffset)
toggleBreakpointTask(sample, { logicalPosition }, checkLine = false) {
text(JavaLessonsBundle.message("java.onboarding.balloon.click.here"),
LearningBalloonConfig(Balloon.Position.below, width = 0, cornerToPointerDistance = 20))
text(JavaLessonsBundle.message("java.onboarding.toggle.breakpoint.1",
code("6.5"), code("findAverage"), code("26")))
text(JavaLessonsBundle.message("java.onboarding.toggle.breakpoint.2"))
}
highlightButtonById("Debug", highlightInside = false, usePulsation = false)
actionTask("Debug") {
showBalloonOnHighlightingComponent(JavaLessonsBundle.message("java.onboarding.balloon.start.debugging"))
restoreState {
lineWithBreakpoints() != setOf(logicalPosition.line)
}
restoreIfModified(sample)
JavaLessonsBundle.message("java.onboarding.start.debugging", icon(AllIcons.Actions.StartDebugger))
}
lateinit var debuggerGotItTaskId: TaskContext.TaskId
task {
debuggerGotItTaskId = taskId
}
highlightDebugActionsToolbar()
task {
rehighlightPreviousUi = true
gotItStep(Balloon.Position.above, width = 0,
JavaLessonsBundle.message("java.onboarding.balloon.about.debug.panel",
strong(UIBundle.message("tool.window.name.debug")),
strong(LessonsBundle.message("debug.workflow.lesson.name"))))
restoreByUi(debuggerGotItTaskId)
}
highlightButtonById("Stop", highlightInside = false, usePulsation = false)
task {
val position = if (UIExperiment.isNewDebuggerUIEnabled()) Balloon.Position.above else Balloon.Position.atRight
showBalloonOnHighlightingComponent(JavaLessonsBundle.message("java.onboarding.balloon.stop.debugging"),
position) { list -> list.maxByOrNull { it.locationOnScreen.y } }
text(JavaLessonsBundle.message("java.onboarding.stop.debugging", icon(AllIcons.Actions.Suspend)))
restoreIfModified(sample)
stateCheck {
XDebuggerManager.getInstance(project).currentSession == null
}
}
prepareRuntimeTask {
LearningUiHighlightingManager.clearHighlights()
}
}
private fun LessonContext.waitIndexingTasks() {
task {
triggerAndBorderHighlight().component { progress: NonOpaquePanel ->
progress.javaClass.name.contains("InlineProgressPanel")
}
}
task {
text(JavaLessonsBundle.message("java.onboarding.indexing.description"))
text(JavaLessonsBundle.message("java.onboarding.wait.indexing"), LearningBalloonConfig(Balloon.Position.above, 0))
waitSmartModeStep()
}
waitBeforeContinue(300)
prepareRuntimeTask {
LearningUiHighlightingManager.clearHighlights()
}
}
private fun LessonContext.runTasks() {
highlightRunToolbar(highlightInside = false, usePulsation = false)
task {
triggerUI {
clearPreviousHighlights = false
}.component { ui: ActionButton -> ActionManager.getInstance().getId(ui.action) == "Run" }
}
task {
val introductionText = JavaLessonsBundle.message("java.onboarding.temporary.configuration.description",
strong(ActionsBundle.actionText("NewUiRunWidget")),
icon(AllIcons.Actions.Execute),
icon(AllIcons.Actions.StartDebugger))
val runOptionsText = if (PlatformUtils.isIdeaUltimate()) {
JavaLessonsBundle.message("java.onboarding.run.options.ultimate",
icon(AllIcons.Actions.Profile),
icon(AllIcons.General.RunWithCoverage),
icon(AllIcons.Actions.More))
}
else {
JavaLessonsBundle.message("java.onboarding.run.options.community",
icon(AllIcons.General.RunWithCoverage),
icon(AllIcons.Actions.More))
}
text("$introductionText $runOptionsText")
text(JavaLessonsBundle.message("java.onboarding.run.sample", icon(AllIcons.Actions.Execute), action("Run")))
text(JavaLessonsBundle.message("java.onboarding.run.sample.balloon", icon(AllIcons.Actions.Execute), action("Run")),
LearningBalloonConfig(Balloon.Position.below, 0))
checkToolWindowState("Run", true)
restoreIfModified(sample)
}
}
private fun LessonContext.openLearnToolwindow() {
task {
triggerAndBorderHighlight().component { stripe: ActionButton ->
stripe.action.templateText == LearnBundle.message("toolwindow.stripe.Learn")
}
}
task {
openLearnTaskId = taskId
text(JavaLessonsBundle.message("java.onboarding.balloon.open.learn.toolbar", strong(LearnBundle.message("toolwindow.stripe.Learn"))),
LearningBalloonConfig(Balloon.Position.atRight, width = 0, duplicateMessage = true))
stateCheck {
ToolWindowManager.getInstance(project).getToolWindow("Learn")?.isVisible == true
}
restoreIfModified(sample)
}
prepareRuntimeTask {
LearningUiHighlightingManager.clearHighlights()
requestEditorFocus()
}
}
private fun LessonContext.checkUiSettings() {
hideToolStripesPreference = uiSettings.hideToolStripes
showNavigationBarPreference = uiSettings.showNavigationBar
showInvalidDebugLayoutWarning()
if (!hideToolStripesPreference && (showNavigationBarPreference || uiSettings.showMainToolbar)) {
// a small hack to have same tasks count. It is needed to track statistics result.
task { }
task { }
return
}
task {
text(JavaLessonsBundle.message("java.onboarding.change.ui.settings"))
proceedLink()
}
prepareRuntimeTask {
uiSettings.hideToolStripes = false
uiSettings.showNavigationBar = true
uiSettings.fireUISettingsChanged()
}
}
private fun LessonContext.projectTasks() {
prepareRuntimeTask {
LessonUtil.hideStandardToolwindows(project)
}
task {
triggerAndBorderHighlight().component { stripe: ActionButton ->
stripe.action.templateText == IdeUICustomization.getInstance().getProjectViewTitle(project)
}
}
lateinit var openProjectViewTask: TaskContext.TaskId
task {
openProjectViewTask = taskId
var projectDirExpanded = false
text(JavaLessonsBundle.message("java.onboarding.project.view.description",
action("ActivateProjectToolWindow")))
text(JavaLessonsBundle.message("java.onboarding.balloon.project.view"),
LearningBalloonConfig(Balloon.Position.atRight, width = 0, cornerToPointerDistance = 8))
triggerUI().treeItem { tree: JTree, path: TreePath ->
val result = path.pathCount >= 2 && path.getPathComponent(1).isToStringContains(learningProjectName)
if (result) {
if (!projectDirExpanded) {
invokeLater { tree.expandPath(path) }
}
projectDirExpanded = true
}
result
}
}
task {
var srcDirCollapsed = false
triggerAndBorderHighlight().treeItem { tree: JTree, path: TreePath ->
val result = path.pathCount >= 3
&& path.getPathComponent(1).isToStringContains(learningProjectName)
&& path.getPathComponent(2).isToStringContains(demoFileDirectory)
if (result) {
if (!srcDirCollapsed) {
invokeLater { tree.collapsePath(path) }
}
srcDirCollapsed = true
}
result
}
}
fun isDemoFilePath(path: TreePath) =
path.pathCount >= 4 && path.getPathComponent(3).isToStringContains(demoFileNameWithoutExtension)
task {
text(JavaLessonsBundle.message("java.onboarding.balloon.source.directory", strong(demoFileDirectory)),
LearningBalloonConfig(Balloon.Position.atRight, duplicateMessage = true, width = 0))
triggerAndBorderHighlight().treeItem { _: JTree, path: TreePath ->
isDemoFilePath(path)
}
restoreByUi(openProjectViewTask)
}
task {
text(JavaLessonsBundle.message("java.onboarding.balloon.open.file", strong(demoFileName)),
LearningBalloonConfig(Balloon.Position.atRight, duplicateMessage = true, width = 0))
stateCheck l@{
if (FileEditorManager.getInstance(project).selectedTextEditor == null) return@l false
virtualFile.name == demoFileName
}
restoreState {
(previous.ui as? JTree)?.takeIf { tree ->
TreeUtil.visitVisibleRows(tree, TreeVisitor { path ->
if (isDemoFilePath(path)) TreeVisitor.Action.INTERRUPT else TreeVisitor.Action.CONTINUE
}) != null
}?.isShowing?.not() ?: true
}
}
}
abstract val completionStepExpectedCompletion: String
private fun LessonContext.completionSteps() {
prepareRuntimeTask {
setSample(sample.insertAtPosition(2, " / values<caret>"))
FocusManagerImpl.getInstance(project).requestFocusInProject(editor.contentComponent, project)
}
task {
val textToFind = "result / values"
triggerOnEditorText(textToFind, centerOffset = textToFind.length)
}
task {
text(JavaLessonsBundle.message("java.onboarding.type.division",
code(" / values")))
text(JavaLessonsBundle.message("java.onboarding.invoke.completion", code(".")))
text(JavaLessonsBundle.message("java.onboarding.invoke.completion.balloon", code(".")),
LearningBalloonConfig(Balloon.Position.below, width = 0))
triggerAndBorderHighlight().listItem { // no highlighting
it.isToStringContains(completionStepExpectedCompletion)
}
proposeRestoreForInvalidText(".")
}
task {
text(JavaLessonsBundle.message("java.onboarding.choose.values.item",
code(completionStepExpectedCompletion), action("EditorChooseLookupItem")))
text(JavaLessonsBundle.message("java.onboarding.invoke.completion.tip", action("CodeCompletion")))
stateCheck {
checkEditorModification(sample, modificationPositionId = 2, needChange = "/values.$completionStepExpectedCompletion")
}
}
}
abstract fun LessonContext.contextActions()
private fun LessonContext.searchEverywhereTasks() {
val toggleCase = ActionsBundle.message("action.EditorToggleCase.text")
caret("AVERAGE", select = true)
task("SearchEverywhere") {
text(JavaLessonsBundle.message("java.onboarding.invoke.search.everywhere.1",
strong(toggleCase), code("AVERAGE")))
text(JavaLessonsBundle.message("java.onboarding.invoke.search.everywhere.2",
LessonUtil.rawKeyStroke(KeyEvent.VK_SHIFT), LessonUtil.actionName(it)))
triggerAndBorderHighlight().component { ui: ExtendableTextField ->
UIUtil.getParentOfType(SearchEverywhereUI::class.java, ui) != null
}
restoreIfModifiedOrMoved()
}
task {
transparentRestore = true
before {
if (backupPopupLocation != null) return@before
val ui = previous.ui ?: return@before
val popupWindow = UIUtil.getParentOfType(JWindow::class.java, ui) ?: return@before
val oldPopupLocation = WindowStateService.getInstance(project).getLocation(SearchEverywhereManagerImpl.LOCATION_SETTINGS_KEY)
if (adjustSearchEverywherePosition(popupWindow, "of array ") || LessonUtil.adjustPopupPosition(project, popupWindow)) {
backupPopupLocation = oldPopupLocation
}
}
text(JavaLessonsBundle.message("java.onboarding.search.everywhere.description",
code("AVERAGE"), code(JavaLessonsBundle.message("toggle.case.part"))))
triggerAndBorderHighlight().listItem { item ->
val value = (item as? GotoActionModel.MatchedValue)?.value
(value as? GotoActionModel.ActionWrapper)?.action is ToggleCaseAction
}
restoreByUi()
restoreIfModifiedOrMoved()
}
task {
text(JavaLessonsBundle.message("java.onboarding.apply.action", strong(toggleCase), LessonUtil.rawEnter()))
stateCheck {
editor.document.text.contains("\"average")
}
restoreByUi(delayMillis = defaultRestoreDelay)
}
text(JavaLessonsBundle.message("java.onboarding.case.changed"))
}
}
@@ -25,5 +25,11 @@
<orderEntry type="module" module-name="kotlin.jvm" />
<orderEntry type="module" module-name="kotlin.idea" />
<orderEntry type="module" module-name="kotlin.base.resources" />
<orderEntry type="module" module-name="intellij.java" />
<orderEntry type="module" module-name="intellij.platform.execution.impl" />
<orderEntry type="module" module-name="intellij.platform.debugger" />
<orderEntry type="module" module-name="intellij.platform.core.ui" />
<orderEntry type="library" name="kotlinx-serialization-core" level="project" />
<orderEntry type="library" name="kotlinx-serialization-json" level="project" />
</component>
</module>
@@ -0,0 +1,14 @@
const val A_CONST = 10000
const val B_CONST = 500
<select>fun calc() { Processor.process(A_CONST, B_CONST) }</select>
class Processor{
companion
object {
fun process(a: Int, b: Int): Int { return a * b + A_CONST
}
}
}
@@ -0,0 +1,9 @@
import javax.swing.*
fun main() {
val frame = JFrame("FrameDemo")
frame.setSize(<caret>)
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
frame.setVisible(true)
}
@@ -0,0 +1,17 @@
import java.text.DecimalFormat
fun main() {
<caret>customFormat("###,###.###", 123456.789)
customFormat("###.##", 123456.789)
customFormat("000000.000", 123.78)
customFormat("$###,###.###", 12345.67)
}
/**
* Prints a double value formatted according to a given pattern.
*/
fun customFormat(pattern: String, value: Double) {
val myFormatter = DecimalFormat(pattern);
val output = myFormatter.format(value);
System.out.println("$value $pattern $output");
}
@@ -4,18 +4,10 @@ import java.io.InputStream
* This is a sample class used in IDE Feature Trainer
* for illustration properties
*/
class BufferedReader {
private var inputStream: InputStream
private var maxBufferSize = 1024
constructor(inputStream: InputStream) {
this.inputStream = inputStream
}
constructor(inputStream: InputStream, maxBufferSize: Int) {
this.inputStream = inputStream
this.maxBufferSize = maxBufferSize
}
class BufferedReader(
private var inputStream: InputStream,
private var maxBufferSize: Int = 1024
) {
/**
* @return one byte from input stream
@@ -0,0 +1,13 @@
open class DerivedClass1 : SomeInterface {
override fun foo(demo: FileStructureDemo) {
demo.boo()
}
}
class SecondLevelClassA : DerivedClass1() {
override fun foo(demo: FileStructureDemo) {
demo.foo()
}
}
class SecondLevelClassB : DerivedClass1()
@@ -0,0 +1,5 @@
class DerivedClass2 : SomeInterface {
override fun foo(demo: FileStructureDemo) {
demo.foo()
}
}
@@ -0,0 +1,7 @@
class InheritanceHierarchySample {
}
fun someEntryPoint(base: SomeInterface, demo: FileStructureDemo) {
base.foo(demo)
}
@@ -13,6 +13,7 @@ class QuadraticEquationsSolver {
val x2 = (-b - sqrt(d)) / (2.0 * a)
println("x1 = $x1, x2 = $x2")
}
else -> println("x = ${-b / (2.0 * a)}")
}
}
@@ -1,4 +1,3 @@
fun main() {
println("Hello World!")
}
@@ -0,0 +1,5 @@
interface SomeInterface {
fun foo(demo: FileStructureDemo)
}
interface SomeDerivedInterface : SomeInterface
@@ -0,0 +1,5 @@
package util
object Utils {
val FRUITS = arrayOf("pineapple", "banana", "apple", "grapes", "mango", "melon", "peach", "orange")
}
@@ -0,0 +1,14 @@
package warehouse
fun main() {
val warehouse = Warehouse()
warehouse.addFruits("peach", 3)
warehouse.addFruits("pineapple", 5)
warehouse.addFruits("mango", 1)
warehouse.addFruits("apple", 5)
val result = warehouse.takeFruit("apple")
if (result) {
println("This apple was delicious!")
}
warehouse.printAllFruits()
}
@@ -0,0 +1,43 @@
package warehouse
import util.Utils
class Warehouse {
// Fruit name to amount of it in warehouse
private val entry: MutableMap<String, Int> = HashMap() // Apple, banana, etc...
init {
val availableFruits: Array<String> = Utils.FRUITS
for (fruit in availableFruits) {
entry[fruit] = 0
}
}
/**
* @param fruitName some fruit name from [Utils.FRUITS] (mango, apple...)
*/
fun addFruits(fruitName: String, quantity: Int) {
val curQuantity = entry[fruitName]
if (curQuantity != null) {
entry[fruitName] = curQuantity + quantity
} else {
throw IllegalArgumentException("Not found fruit with name: $fruitName")
}
}
fun takeFruit(fruitName: String): Boolean {
val curQuantity = entry[fruitName]
requireNotNull(curQuantity) { "Not found fruit with name: $fruitName" }
if (curQuantity > 0) {
entry[fruitName] = curQuantity - 1
return true
}
return false
}
fun printAllFruits() {
for ((key, value) in entry) {
println("$key: $value")
}
}
}
@@ -1,3 +1,19 @@
configure.kotlin.progress.title=Configure Kotlin
kotlin.basic.completion.choose.item=Choose the highlighted {0} item to complete the function call and place the caret inside the parenthesis.
kotlin.onboarding.invoke.intention.for.warning.1=You have just fixed a bug, but you can make this code look even better. \
IDEA highlights and adds a yellow bulb to the code lines that can be improved.
kotlin.onboarding.invoke.intention.for.warning.2=Press {0} to preview the warnings and apply a quick-fix.
kotlin.onboarding.invoke.intention.for.warning.balloon=Press {0} to show available quick-fixes
kotlin.onboarding.select.fix=Apply the first item: {0}. In this case, the <strong>for-each</strong> loop will make code easier to understand.
kotlin.onboarding.invoke.intention.for.code=Intentions also save your time and make coding easier. Let''s use an intention to reformat string concatenation. \
Press {0} to show possible options.
kotlin.onboarding.invoke.intention.for.code.balloon=Press {0} to show available intentions
kotlin.onboarding.apply.intention=Select {0} and press {1}.
kotlin.basic.completion.choose.item=Choose the highlighted {0} item to complete the function call and place the caret inside the parenthesis.
# Next string does not require translation
kotlin.refactoring.menu.inline.property.eng=Now let''s replace single usage of the {0} variable with an expression that defines it. \
You can press {1} and filter the refactoring menu by <strong>ipr</strong> (<strong>i</strong>nline <strong>pr</strong>operty). \
Choose this item or press {2}.
kotlin.refactoring.menu.confirm.constant=Choose and type any new name you want to use for the constant. Then press {0} to confirm.
@@ -18,6 +18,7 @@ import java.nio.file.Path
class KotlinLangSupport : JavaBasedLangSupport() {
override val contentRootDirectoryName = "KotlinLearningProject"
override val defaultProductName: String = "IDEA"
override val primaryLanguage: String = "kotlin"
override val scratchFileName: String = "Learning.kt"
@@ -1,22 +1,55 @@
// Copyright 2000-2022 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 org.jetbrains.kotlin.training.ift
import com.intellij.java.ift.JavaLessonsBundle
import com.intellij.util.PlatformUtils
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.training.ift.lesson.basic.KotlinBasicCompletionLesson
import org.jetbrains.kotlin.training.ift.lesson.basic.KotlinContextActionsLesson
import org.jetbrains.kotlin.training.ift.lesson.basic.KotlinSelectLesson
import org.jetbrains.kotlin.training.ift.lesson.basic.KotlinSurroundAndUnwrapLesson
import org.jetbrains.kotlin.training.ift.lesson.completion.KotlinCompletionWithTabLesson
import org.jetbrains.kotlin.training.ift.lesson.completion.KotlinPostfixCompletionLesson
import org.jetbrains.kotlin.training.ift.lesson.completion.KotlinSmartTypeCompletionLesson
import org.jetbrains.kotlin.training.ift.lesson.essential.KotlinOnboardingTourLesson
import org.jetbrains.kotlin.training.ift.lesson.navigation.KotlinDeclarationAndUsagesLesson
import org.jetbrains.kotlin.training.ift.lesson.navigation.KotlinFileStructureLesson
import org.jetbrains.kotlin.training.ift.lesson.navigation.KotlinRecentFilesLesson
import org.jetbrains.kotlin.training.ift.lesson.navigation.KotlinSearchEverywhereLesson
import org.jetbrains.kotlin.training.ift.lesson.refactorings.KotlinRefactoringMenuLesson
import training.dsl.LessonUtil
import training.learn.CourseManager
import training.learn.LessonsBundle
import training.learn.course.LearningCourseBase
import training.learn.course.LearningModule
import training.learn.course.LessonType
import training.learn.lesson.general.*
import training.learn.lesson.general.assistance.CodeFormatLesson
import training.learn.lesson.general.assistance.LocalHistoryLesson
import training.learn.lesson.general.assistance.ParameterInfoLesson
import training.learn.lesson.general.assistance.QuickPopupsLesson
import training.learn.lesson.general.navigation.FindInFilesLesson
class KotlinLearningCourse : LearningCourseBase(KotlinLanguage.INSTANCE.id) {
override fun modules() = stableModules()
override fun modules() = onboardingTour() + stableModules() + CourseManager.instance.findCommonModules("Git")
private val isOnboardingLessonEnabled: Boolean
get() = PlatformUtils.isIdeaCommunity() || PlatformUtils.isIdeaUltimate()
private fun onboardingTour() = if (isOnboardingLessonEnabled) listOf(
LearningModule(
id = "Kotlin.Onboarding",
name = JavaLessonsBundle.message("java.onboarding.module.name"),
description = JavaLessonsBundle.message("java.onboarding.module.description", LessonUtil.productName),
primaryLanguage = langSupport,
moduleType = LessonType.PROJECT
) {
listOf(
KotlinOnboardingTourLesson()
)
}
)
else emptyList()
private fun stableModules() = listOf(
LearningModule(
@@ -59,7 +92,44 @@ class KotlinLearningCourse : LearningCourseBase(KotlinLanguage.INSTANCE.id) {
primaryLanguage = langSupport,
moduleType = LessonType.SINGLE_EDITOR // todo: change to SCRATCH when KTIJ-20742 will be resolved
) {
listOf(KotlinBasicCompletionLesson())
listOf(
KotlinBasicCompletionLesson(),
KotlinSmartTypeCompletionLesson(),
KotlinPostfixCompletionLesson(),
// TODO: KotlinStatementCompletionLesson
KotlinCompletionWithTabLesson()
)
},
LearningModule(
id = "Kotlin.Refactorings",
name = LessonsBundle.message("refactorings.module.name"),
description = LessonsBundle.message("refactorings.module.description"),
primaryLanguage = langSupport,
moduleType = LessonType.SINGLE_EDITOR
) {
fun ls(sampleName: String) = loadSample("Refactorings/$sampleName")
listOf(
// TODO: KotlinRenameLesson(),
// TODO: ExtractVariableFromBubbleLesson(ls("ExtractVariable.java.sample")),
// TODO: KotlinExtractMethodCocktailSortLesson(),
KotlinRefactoringMenuLesson(),
)
},
LearningModule(
id = "Kotlin.CodeAssistance",
name = LessonsBundle.message("code.assistance.module.name"),
description = LessonsBundle.message("code.assistance.module.description"),
primaryLanguage = langSupport,
moduleType = LessonType.SINGLE_EDITOR
) {
fun ls(sampleName: String) = loadSample("CodeAssistance/$sampleName")
listOf(
LocalHistoryLesson(),
CodeFormatLesson(ls("CodeFormat.kt.sample"), true),
ParameterInfoLesson(ls("ParameterInfo.kt.sample")),
QuickPopupsLesson(ls("QuickPopups.kt.sample")),
// TODO: KotlinEditorCodingAssistanceLesson(ls("EditorCodingAssistance.java.sample")),
)
},
LearningModule(
id = "Kotlin.Navigation",
@@ -70,12 +140,27 @@ class KotlinLearningCourse : LearningCourseBase(KotlinLanguage.INSTANCE.id) {
) {
listOf(
KotlinSearchEverywhereLesson(),
FindInFilesLesson("src/warehouse/FindInFilesSample.kt"),
KotlinFileStructureLesson(),
KotlinDeclarationAndUsagesLesson(),
// TODO: KotlinInheritanceHierarchyLesson
KotlinRecentFilesLesson()
// TODO: KotlinOccurrencesLesson
)
}
},
/*LearningModule(id = "Kotlin.RunAndDebug",
name = LessonsBundle.message("run.debug.module.name"),
description = LessonsBundle.message("run.debug.module.description"),
primaryLanguage = langSupport,
moduleType = LessonType.SINGLE_EDITOR) {
listOf(
// TODO: KotlinRunConfigurationLesson(),
// TODO: KotlinDebugLesson(),
)
},*/
)
override fun getLessonIdToTipsMap(): Map<String, List<String>> = mapOf(
override fun getLessonIdToTipsMap(): Map<String, List<String>> = mutableMapOf(
// Essential
"context.actions" to listOf("ContextActions"),
"Actions" to listOf("find_action", "GoToAction"),
@@ -94,6 +179,12 @@ class KotlinLearningCourse : LearningCourseBase(KotlinLanguage.INSTANCE.id) {
// Navigation
"Search everywhere" to listOf("SearchEverywhere", "GoToClass", "search_everywhere_general"),
"Find in files" to listOf("FindReplaceToggle", "FindInPath"),
"File structure" to listOf("FileStructurePopup"),
)
).also { map ->
val gitCourse = CourseManager.instance.findCommonCourseById("Git")
if (gitCourse != null) {
map.putAll(gitCourse.getLessonIdToTipsMap())
}
}
}
@@ -0,0 +1,19 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.training.ift.lesson.completion
import training.dsl.LessonSample
import training.dsl.parseLessonSample
import training.learn.lesson.general.CompletionWithTabLesson
class KotlinCompletionWithTabLesson :
CompletionWithTabLesson("DO_NOTHING_ON_CLOSE") {
override val sample: LessonSample = parseLessonSample("""import javax.swing.*
fun main() {
val frame = JFrame("FrameDemo")
frame.setSize(175, 100)
frame.setDefaultCloseOperation(WindowConstants.<caret>DISPOSE_ON_CLOSE)
frame.isVisible = true
}""".trimIndent())
}
@@ -0,0 +1,40 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.training.ift.lesson.completion
import com.intellij.java.ift.JavaLessonsBundle
import training.dsl.LearningDslBase
import training.dsl.LessonSample
import training.dsl.parseLessonSample
import training.learn.lesson.general.completion.PostfixCompletionLesson
class KotlinPostfixCompletionLesson : PostfixCompletionLesson() {
override val sample: LessonSample = parseLessonSample(
"""
fun demonstrate(showTimes: Int) {
showTimes<caret>
}
""".trimIndent()
)
override val result: String = parseLessonSample(
"""
fun demonstrate(showTimes: Int) {
for (i in 0 until showTimes) {
}
}
""".trimIndent()
).text
override val completionSuffix: String = ".fo"
override val completionItem: String = "fori"
override fun LearningDslBase.getTypeTaskText(): String {
return JavaLessonsBundle.message("java.postfix.completion.type", code(completionSuffix))
}
override fun LearningDslBase.getCompleteTaskText(): String {
return JavaLessonsBundle.message("java.postfix.completion.complete", code(completionItem), action("EditorChooseLookupItem"))
}
}
@@ -0,0 +1,33 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.training.ift.lesson.completion
import com.intellij.java.ift.lesson.completion.SmartTypeCompletionLessonBase
import training.dsl.LessonContext
import training.dsl.LessonSample
import training.dsl.parseLessonSample
class KotlinSmartTypeCompletionLesson : SmartTypeCompletionLessonBase() {
override val sample: LessonSample = parseLessonSample("""
object TestObject
fun smartCompletionDemo(): TestObject {
val iterations = 5
repeat(<caret>) {
println("Hello")
}
return
}
""".trimIndent())
override val firstCompletionItem: String = "iterations"
override val firstCompletionCheck: String = "repeat(iterations)"
override val secondCompletionItem: String = "TestObject"
override val secondCompletionCheck: String = "return TestObject"
override fun LessonContext.setCaretForSecondItem() {
caret(9, 12)
}
}
@@ -0,0 +1,92 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.training.ift.lesson.essential
import com.intellij.java.ift.lesson.essential.OnboardingTourLessonBase
import com.intellij.openapi.ui.popup.Balloon
import org.jetbrains.annotations.Nls
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.training.ift.KotlinLessonsBundle
import training.dsl.*
import training.dsl.LessonUtil.restoreIfModifiedOrMoved
import training.util.*
class KotlinOnboardingTourLesson : OnboardingTourLessonBase("kotlin.onboarding") {
override val demoFileExtension: String = "kt"
override val learningProjectName: String = "KotlinLearningProject"
private val samplePrintln = "println(\"AVERAGE of array \" + array.joinToString() + \" is \" + findAverage(array))"
override val sample: LessonSample = parseLessonSample("""
fun findAverage(values: IntArray): Double {
var result = 0.0
for (i in 0 un<caret id=3/>til values.size) {
result += values[i]
}
<caret>return result<caret id=2/>
}
fun main() {
val array = intArrayOf(5, 6, 7, 8)
$samplePrintln
}
""".trimIndent())
override val completionStepExpectedCompletion: String = "size"
override fun LessonContext.contextActions() {
val quickFixMessage = KotlinBundle.message("replace.index.loop.with.collection.loop.quick.fix.text")
caret(sample.getPosition(3))
task {
triggerOnEditorText("until", highlightBorder = true)
}
task("ShowIntentionActions") {
text(KotlinLessonsBundle.message("kotlin.onboarding.invoke.intention.for.warning.1"))
text(KotlinLessonsBundle.message("kotlin.onboarding.invoke.intention.for.warning.2", action(it)))
text(KotlinLessonsBundle.message("kotlin.onboarding.invoke.intention.for.warning.balloon", action(it)),
LearningBalloonConfig(Balloon.Position.above, width = 0, cornerToPointerDistance = 80))
triggerAndBorderHighlight().listItem { item ->
item.isToStringContains(quickFixMessage)
}
restoreIfModifiedOrMoved()
}
task {
text(KotlinLessonsBundle.message("kotlin.onboarding.select.fix", strong(quickFixMessage)))
stateCheck {
editor.document.text.contains("for (element in values)")
}
restoreByUi(delayMillis = defaultRestoreDelay)
}
fun getIntentionMessage(): @Nls String {
return KotlinBundle.message("convert.concatenation.to.template")
}
caret("RAGE")
task {
triggerOnEditorText("AVERAGE")
}
task("ShowIntentionActions") {
text(KotlinLessonsBundle.message("kotlin.onboarding.invoke.intention.for.code", action(it)))
text(KotlinLessonsBundle.message("kotlin.onboarding.invoke.intention.for.code.balloon", action(it)),
LearningBalloonConfig(Balloon.Position.below, width = 0))
val intentionMessage = getIntentionMessage()
triggerAndBorderHighlight().listItem { item ->
item.isToStringContains(intentionMessage)
}
restoreIfModifiedOrMoved()
}
task {
text(KotlinLessonsBundle.message("kotlin.onboarding.apply.intention", strong(getIntentionMessage()), LessonUtil.rawEnter()))
stateCheck {
val text = editor.document.text
text.contains("\${array.joinToString()}")
}
restoreByUi(delayMillis = defaultRestoreDelay)
}
}
}
@@ -0,0 +1,18 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.training.ift.lesson.navigation
import com.intellij.psi.PsiElement
import com.intellij.psi.util.parentOfType
import org.jetbrains.kotlin.psi.KtCallExpression
import training.dsl.LessonContext
import training.learn.lesson.general.navigation.DeclarationAndUsagesLesson
class KotlinDeclarationAndUsagesLesson : DeclarationAndUsagesLesson() {
override fun LessonContext.setInitialPosition(): Unit = caret("foo()")
override val sampleFilePath: String get() = "src/DerivedClass2.kt"
override val entityName: String = "foo"
override fun getParentExpression(element: PsiElement): PsiElement? {
return element.takeIf { element.parentOfType<KtCallExpression>() != null }
}
}
@@ -0,0 +1,25 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.training.ift.lesson.navigation
import training.dsl.LessonContext
import training.dsl.LessonUtil
import training.learn.LessonsBundle
import training.learn.lesson.general.navigation.RecentFilesLesson
class KotlinRecentFilesLesson : RecentFilesLesson() {
override val sampleFilePath: String = "src/RecentFilesDemo.kt"
override val transitionMethodName: String = "println"
override val transitionFileName: String = "Console"
override val stringForRecentFilesSearch: String = "print"
override fun LessonContext.setInitialPosition(): Unit = caret("println")
override val helpLinks: Map<String, String>
get() = mapOf(
Pair(
LessonsBundle.message("recent.files.locations.help.link"),
LessonUtil.getHelpLink("idea", "discover-intellij-idea.html#recent-files")
),
)
}
@@ -0,0 +1,86 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.training.ift.lesson.refactorings
import com.intellij.java.ift.JavaLessonsBundle
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.training.ift.KotlinLessonsBundle
import training.dsl.LessonContext
import training.dsl.LessonSample
import training.dsl.LessonUtil
import training.dsl.LessonUtil.restoreIfModifiedOrMoved
import training.dsl.dropMnemonic
import training.dsl.parseLessonSample
import training.dsl.restoreRefactoringOptionsInformer
import training.learn.lesson.general.refactorings.RefactoringMenuLessonBase
import training.util.adaptToNotNativeLocalization
class KotlinRefactoringMenuLesson : RefactoringMenuLessonBase("java.refactoring.menu") {
override val sample: LessonSample = parseLessonSample(
"""
import java.io.FileReader
import java.io.BufferedReader
fun main() {
val list = readStrings()
val filtered = list.filter { it.isNotEmpty() }
filtered.forEach {
println(it)
}
}
fun readStrings(): List<String> {
return BufferedReader(<select>FileReader("input.txt")</select>).readLines()
}""".trimIndent())
override val lessonContent: LessonContext.() -> Unit = {
extractParameterTasks()
moreRefactoringsTasks()
restoreRefactoringOptionsInformer()
}
private fun LessonContext.moreRefactoringsTasks() {
waitBeforeContinue(300)
val inlineVariableName = "list"
caret(inlineVariableName)
actionTask("Inline") {
restoreIfModifiedOrMoved()
if (adaptToNotNativeLocalization) {
JavaLessonsBundle.message(
"java.refactoring.menu.inline.variable", code(inlineVariableName),
action("Refactorings.QuickListPopupAction"), strong(KotlinBundle.message("title.inline.property")),
action(it)
)
} else KotlinLessonsBundle.message(
"kotlin.refactoring.menu.inline.property.eng",
code(inlineVariableName), action("Refactorings.QuickListPopupAction"), action(it)
)
}
task {
stateCheck {
!editor.document.charsSequence.contains(inlineVariableName)
}
}
caret("txt", true)
actionTask("IntroduceConstant") {
restoreIfModifiedOrMoved()
if (adaptToNotNativeLocalization) {
JavaLessonsBundle.message(
"java.refactoring.menu.introduce.constant", action("Refactorings.QuickListPopupAction"),
strong(KotlinBundle.message("introduce.constant").dropMnemonic()), action(it)
)
} else JavaLessonsBundle.message(
"java.refactoring.menu.introduce.constant.eng",
action("Refactorings.QuickListPopupAction"), action(it)
)
}
actionTask("NextTemplateVariable") {
KotlinLessonsBundle.message("kotlin.refactoring.menu.confirm.constant", LessonUtil.rawEnter())
}
}
}