[IFT] Add Find and Replace in files lesson for java, python and ruby

IDEA-CR-69975

GitOrigin-RevId: 0db188d67ae1a5c294047e7e4eb95fe01d5f5e85
This commit is contained in:
Konstantin Hudyakov
2021-01-11 13:40:24 +00:00
committed by intellij-monorepo-bot
parent 59013f4bda
commit 76db4f66dc
11 changed files with 374 additions and 3 deletions
@@ -0,0 +1,5 @@
package util;
public final class Utils {
public static final String[] FRUITS = new String[]{"pineapple", "banana", "apple", "grapes", "mango", "melon", "peach", "orange"};
}
@@ -0,0 +1,18 @@
package warehouse;
public final class FindInFilesSample {
public static void main(String[] args) {
Warehouse warehouse = new Warehouse();
warehouse.addFruits("peach", 3);
warehouse.addFruits("pineapple", 5);
warehouse.addFruits("mango", 1);
warehouse.addFruits("apple", 5);
boolean result = warehouse.takeFruit("apple");
if (result) {
System.out.println("This apple was delicious!");
}
warehouse.printAllFruits();
}
}
@@ -0,0 +1,49 @@
package warehouse;
import java.util.HashMap;
import java.util.Map;
import util.Utils;
public final class Warehouse {
// Fruit name to amount of it in warehouse
private final Map<String, Integer> entry = new HashMap<>(); // Apple, banana, etc...
public Warehouse() {
String[] availableFruits = Utils.FRUITS;
for (String fruit : availableFruits) {
entry.put(fruit, 0);
}
}
/**
* @param fruitName some fruit name from Utils.FRUITS (mango, apple...)
*/
public void addFruits(String fruitName, int quantity) {
Integer curQuantity = entry.get(fruitName);
if (curQuantity != null) {
entry.put(fruitName, curQuantity + quantity);
}
else {
throw new IllegalArgumentException("Not found fruit with name: " + fruitName);
}
}
public boolean takeFruit(String fruitName) {
Integer curQuantity = entry.get(fruitName);
if (curQuantity == null) {
throw new IllegalArgumentException("Not found fruit with name: " + fruitName);
}
else if (curQuantity > 0) {
entry.put(fruitName, curQuantity - 1);
return true;
}
return false;
}
public void printAllFruits() {
for (Map.Entry<String, Integer> pair : entry.entrySet()) {
System.out.println(pair.getKey() + ": " + pair.getValue());
}
}
}
@@ -21,6 +21,7 @@ import training.learn.lesson.general.*
import training.learn.lesson.general.assistance.CodeFormatLesson
import training.learn.lesson.general.assistance.ParameterInfoLesson
import training.learn.lesson.general.assistance.QuickPopupsLesson
import training.learn.lesson.general.navigation.FindInFilesLesson
import training.learn.lesson.general.refactorings.ExtractVariableFromBubbleLesson
import training.learn.lesson.kimpl.LessonUtil
@@ -100,6 +101,7 @@ class JavaLearningCourse : LearningCourseBase(JavaLanguage.INSTANCE.id) {
JavaInheritanceHierarchyLesson(it),
JavaRecentFilesLesson(it),
JavaOccurrencesLesson(it),
FindInFilesLesson(it, lang, "src/warehouse/FindInFilesSample.java")
)
},
LearningModule(name = LessonsBundle.message("run.debug.module.name"),
@@ -247,6 +247,23 @@ search.everywhere.finish=<strong>Done!</strong> Similarly, you can use {0} to lo
to look for a file.
search.everywhere.navigation.promotion=You will find other navigation actions and workflow use cases in the {0} module.
find.in.files.lesson.name=Find and replace in files
find.in.files.show.find.popup=Suppose you want to find all occurrences of some string in the project. Press {0} to open the {1} window.
find.in.files.type.to.find=Let''s type {0} to look for all textual occurrences in the Learning project.
find.in.files.whole.words=You were supposed to find the <code>apple</code> string but also got some <code>pineapple</code> entries. \
Let''s narrow down the search to a whole word. Click on {0} or press {1}.
find.in.files.select.row=You can see the context of the found string in the embedded editor. \
Click the highlighted row or navigate to it by {0} and {1} buttons.
find.in.files.go.to.file=Also, you can open the selected file in the main editor. Press {0} or double click the row to move to this file.
find.in.files.show.replace.popup=Suppose you want to replace all found occurrences with other string. \
It may be very boring to perform replacing in each file consistently. <ide/> will help you with this task. Press {0} to open {1} window.
find.in.files.type.to.replace=Let''s replace all occurrences of {0} with {1}. Type {1} in the highlighted field.
find.in.files.select.directory=Also you can search or replace in the specified scope. \
Click on the {0} button or press {1} to narrow the replacement only to current directory.
find.in.files.press.replace.all=Press {0} to start refactoring.
find.in.files.confirm.replace=And now press {0} to confirm refactoring.
find.in.files.popup.closed.warning.message=Press {0} to open the {1} window again.
extract.method.lesson.name=Extract method
extract.method.invoke.action=Press {0} to extract the selected code block into a method.
extract.method.start.refactoring=Click {0} to start refactoring.
@@ -0,0 +1,231 @@
// 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.learn.lesson.general.navigation
import com.intellij.find.FindBundle
import com.intellij.find.FindInProjectSettings
import com.intellij.find.FindManager
import com.intellij.find.SearchTextArea
import com.intellij.find.impl.FindInProjectSettingsBase
import com.intellij.find.impl.FindPopupPanel
import com.intellij.icons.AllIcons
import com.intellij.openapi.actionSystem.impl.ActionButton
import com.intellij.testGuiFramework.fixtures.extended.ExtendedTableFixture
import com.intellij.testGuiFramework.framework.GuiTestUtil
import com.intellij.testGuiFramework.impl.actionButton
import com.intellij.testGuiFramework.impl.button
import com.intellij.testGuiFramework.impl.findComponentWithTimeout
import com.intellij.testGuiFramework.util.Key
import com.intellij.usages.UsagePresentation
import com.intellij.util.ui.UIUtil
import org.fest.swing.core.MouseClickInfo
import org.fest.swing.data.TableCell
import org.fest.swing.fixture.JTextComponentFixture
import training.commands.kotlin.TaskContext
import training.commands.kotlin.TaskRuntimeContext
import training.commands.kotlin.TaskTestContext
import training.learn.LessonsBundle
import training.learn.interfaces.Module
import training.learn.lesson.kimpl.*
import java.awt.event.InputEvent
import java.awt.event.KeyEvent
import javax.swing.*
class FindInFilesLesson(module: Module, lang: String, override val existedFile: String)
: KLesson("Find in files", LessonsBundle.message("find.in.files.lesson.name"), module, lang) {
override val lessonContent: LessonContext.() -> Unit = {
resetFindSettings()
lateinit var showPopupTaskId: TaskContext.TaskId
task("FindInPath") {
showPopupTaskId = taskId
text(LessonsBundle.message("find.in.files.show.find.popup",
action(it), LessonUtil.actionName(it)))
triggerByUiComponentAndHighlight(false, false) { popup: FindPopupPanel ->
!popup.helper.isReplaceState
}
test {
Thread.sleep(300)
actions(it)
}
}
task("apple") {
text(LessonsBundle.message("find.in.files.type.to.find", code(it)))
stateCheck { getFindPopup()?.stringToFind?.toLowerCase() == it }
restoreByUi()
test { type(it) }
}
task {
val wholeWordsButtonText = FindBundle.message("find.whole.words").dropMnemonic()
text(LessonsBundle.message("find.in.files.whole.words",
icon(AllIcons.Actions.Words),
LessonUtil.rawKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.ALT_DOWN_MASK))))
highlightAndTriggerWhenButtonSelected(wholeWordsButtonText)
showWarningIfPopupClosed(false)
test {
ideFrame {
actionButton(wholeWordsButtonText).click()
}
}
}
task("apple...") {
text(LessonsBundle.message("find.in.files.select.row",
action("EditorUp"), action("EditorDown")))
triggerByPartOfComponent { table: JTable ->
val rowIndex = table.findLastRowIndexOfItemWithText(it)
if (rowIndex >= 0) {
table.getCellRect(rowIndex, 0, false)
}
else null
}
triggerByUiComponentAndHighlight(false, false) { table: JTable ->
table.selectedRow != -1 && table.selectedRow == table.findLastRowIndexOfItemWithText(it)
}
restoreByUi(restoreId = showPopupTaskId)
test {
ideFrame {
Thread.sleep(300)
val table = findComponentWithTimeout { table: JTable -> table.findLastRowIndexOfItemWithText(it) != -1 }
val tableFixture = ExtendedTableFixture(robot(), table)
val rowIndex = table.findLastRowIndexOfItemWithText(it)
tableFixture.click(TableCell.row(rowIndex).column(0), MouseClickInfo.leftButton())
}
}
}
task {
text(LessonsBundle.message("find.in.files.go.to.file", LessonUtil.rawEnter()))
stateCheck { virtualFile.name != existedFile.substringAfterLast('/') }
restoreByUi(restoreId = showPopupTaskId)
test { GuiTestUtil.shortcut(Key.ENTER) }
}
task("ReplaceInPath") {
text(LessonsBundle.message("find.in.files.show.replace.popup",
action(it), LessonUtil.actionName(it)))
triggerByUiComponentAndHighlight(false, false) { popup: FindPopupPanel ->
popup.helper.isReplaceState
}
test { actions(it) }
}
task("orange") {
text(LessonsBundle.message("find.in.files.type.to.replace",
code("apple"), code(it)))
triggerByUiComponentAndHighlight(highlightInside = false) { ui: SearchTextArea ->
it.startsWith(ui.textArea.text)
}
stateCheck {
getFindPopup()?.helper?.model?.let { model ->
model.stringToReplace == it && model.stringToFind == "apple"
} ?: false
}
restoreByUi()
test {
ideFrame {
val textArea = findComponentWithTimeout { textArea: JTextArea -> textArea.text == "" }
JTextComponentFixture(robot(), textArea).click()
type(it)
}
}
}
task {
val directoryScopeText = FindBundle.message("find.popup.scope.directory").dropMnemonic()
text(LessonsBundle.message("find.in.files.select.directory",
strong(directoryScopeText),
LessonUtil.rawKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_D, InputEvent.ALT_DOWN_MASK))))
highlightAndTriggerWhenButtonSelected(directoryScopeText)
showWarningIfPopupClosed(true)
test {
ideFrame {
actionButton(directoryScopeText).click()
}
}
}
val replaceAllDialogTitle = FindBundle.message("find.replace.all.confirmation.title")
task {
val replaceAllButtonText = FindBundle.message("find.popup.replace.all.button").dropMnemonic()
text(LessonsBundle.message("find.in.files.press.replace.all", strong(replaceAllButtonText)))
triggerByUiComponentAndHighlight { button: JButton ->
button.text == replaceAllButtonText
}
triggerByUiComponentAndHighlight(false, false) { dialog: JDialog ->
dialog.title == replaceAllDialogTitle
}
showWarningIfPopupClosed(true)
test {
ideFrame {
Thread.sleep(300)
button(replaceAllButtonText).click()
}
}
}
task {
val replaceButtonText = FindBundle.message("find.replace.command")
text(LessonsBundle.message("find.in.files.confirm.replace", strong(replaceButtonText)))
stateCheck { editor.document.charsSequence.contains("orange") }
restoreByUi(delayMillis = defaultRestoreDelay)
test {
ideFrame {
Thread.sleep(300)
findMessageDialog(replaceAllDialogTitle).click(replaceButtonText)
}
}
}
}
private fun TaskRuntimeContext.getFindPopup(): FindPopupPanel? {
return UIUtil.getParentOfType(FindPopupPanel::class.java, focusOwner)
}
private fun TaskContext.highlightAndTriggerWhenButtonSelected(buttonText: String) {
triggerByUiComponentAndHighlight { button: ActionButton ->
button.action.templateText == buttonText
}
triggerByUiComponentAndHighlight(false, false) { button: ActionButton ->
button.action.templateText == buttonText && button.isSelected
}
}
private fun JTable.findLastRowIndexOfItemWithText(textToFind: String): Int {
for (ind in (rowCount - 1) downTo 0) {
val item = getValueAt(ind, 0) as? UsagePresentation
if (item?.plainText?.contains(textToFind, true) == true) {
return ind
}
}
return -1
}
private fun TaskContext.showWarningIfPopupClosed(isReplacePopup: Boolean) {
val actionId = if (isReplacePopup) "ReplaceInPath" else "FindInPath"
showWarning(LessonsBundle.message("find.in.files.popup.closed.warning.message", action(actionId), LessonUtil.actionName(actionId))) {
getFindPopup()?.helper?.isReplaceState != isReplacePopup
}
}
private fun LessonContext.resetFindSettings() {
prepareRuntimeTask {
FindManager.getInstance(project).findInProjectModel.apply {
isWholeWordsOnly = false
stringToFind = ""
stringToReplace = ""
directoryName = null
}
val settings = FindInProjectSettings.getInstance(project) as? FindInProjectSettingsBase
settings?.apply {
findStrings.clear()
replaceStrings.clear()
recentDirectories.clear()
}
}
}
override val testScriptProperties = TaskTestContext.TestScriptProperties(10)
}
@@ -1,9 +1,7 @@
// 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.learn.lesson.kimpl
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.DataProvider
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.actionSystem.impl.ActionButton
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ApplicationNamesInfo
@@ -173,6 +171,10 @@ object LessonUtil {
return "<raw_action>$keyStroke</raw_action>"
}
fun rawKeyStroke(keyStroke: KeyStroke): String {
return "<raw_action>${KeymapUtil.getKeyStrokeText(keyStroke)}</raw_action>"
}
fun rawEnter(): String = rawKeyStroke(KeyEvent.VK_ENTER)
fun rawCtrlEnter(): String {
@@ -0,0 +1 @@
FRUITS = ['pineapple', 'banana', 'apple', 'grapes', 'mango', 'melon', 'peach', 'orange']
@@ -0,0 +1,13 @@
from warehouse.warehouse import Warehouse
warehouse = Warehouse()
warehouse.add_fruits('peach', 3)
warehouse.add_fruits('pineapple', 5)
warehouse.add_fruits('mango', 1)
warehouse.add_fruits('apple', 5)
result = warehouse.take_fruit('apple')
if result:
print('This apple was delicious!')
warehouse.print_all_fruits()
@@ -0,0 +1,31 @@
from util.util import FRUITS
class Warehouse:
# Fruit name to amount of it in warehouse
entry = {} # Apple, banana, etc...
def __init__(self) -> None:
for fruit in FRUITS:
self.entry[fruit] = 0
# fruit name from util.FRUITS (mango, apple...)
def add_fruits(self, fruit_name, quantity) -> None:
cur_quantity = self.entry.get(fruit_name)
if cur_quantity is not None:
self.entry[fruit_name] = cur_quantity + quantity
else:
raise KeyError(f"Not found fruit with name: {fruit_name}")
def take_fruit(self, fruit_name) -> bool:
cur_quantity = self.entry.get(fruit_name)
if cur_quantity is None:
raise KeyError(f"Not found fruit with name: {fruit_name}")
elif cur_quantity > 0:
self.entry[fruit_name] = cur_quantity - 1
return True
return False
def print_all_fruits(self) -> None:
for fruit, quantity in self.entry.items():
print(f"{fruit}: {quantity}")
@@ -26,6 +26,7 @@ import training.learn.lesson.general.*
import training.learn.lesson.general.assistance.CodeFormatLesson
import training.learn.lesson.general.assistance.ParameterInfoLesson
import training.learn.lesson.general.assistance.QuickPopupsLesson
import training.learn.lesson.general.navigation.FindInFilesLesson
import training.learn.lesson.general.refactorings.ExtractMethodCocktailSortLesson
import training.learn.lesson.general.refactorings.ExtractVariableFromBubbleLesson
import training.learn.lesson.kimpl.LessonUtil
@@ -111,6 +112,7 @@ class PythonLearningCourse : LearningCourseBase(PythonLanguage.INSTANCE.id) {
PythonFileStructureLesson(it),
PythonRecentFilesLesson(it),
PythonSearchEverywhereLesson(it),
FindInFilesLesson(it, lang, "src/warehouse/find_in_files_sample.py")
)
},
LearningModule(name = LessonsBundle.message("run.debug.module.name"),