summaryrefslogtreecommitdiff
path: root/python/edu/learn-python/src/com/jetbrains/python/edu/actions
diff options
context:
space:
mode:
Diffstat (limited to 'python/edu/learn-python/src/com/jetbrains/python/edu/actions')
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyCheckAction.java340
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyEditInputAction.java213
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextStudyTaskAction.java24
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextWindowAction.java32
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyPrevWindowAction.java34
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyPreviousStudyTaskAction.java25
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskAction.java122
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRunAction.java89
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyShowHintAction.java95
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyTaskNavigationAction.java97
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyWindowNavigationAction.java65
11 files changed, 1136 insertions, 0 deletions
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyCheckAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyCheckAction.java
new file mode 100644
index 000000000000..f8e10c9c4521
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyCheckAction.java
@@ -0,0 +1,340 @@
+package com.jetbrains.python.edu.actions;
+
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.configurations.GeneralCommandLine;
+import com.intellij.ide.projectView.ProjectView;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.fileEditor.FileEditor;
+import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.ui.popup.Balloon;
+import com.intellij.openapi.ui.popup.BalloonBuilder;
+import com.intellij.openapi.ui.popup.JBPopupFactory;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.JBColor;
+import com.jetbrains.python.edu.StudyDocumentListener;
+import com.jetbrains.python.edu.StudyTaskManager;
+import com.jetbrains.python.edu.StudyUtils;
+import com.jetbrains.python.edu.course.*;
+import com.jetbrains.python.edu.editor.StudyEditor;
+import com.jetbrains.python.sdk.PythonSdkType;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.awt.*;
+import java.io.*;
+import java.util.*;
+import java.util.List;
+
+public class StudyCheckAction extends DumbAwareAction {
+
+ private static final Logger LOG = Logger.getInstance(StudyCheckAction.class.getName());
+ public static final String PYTHONPATH = "PYTHONPATH";
+
+ static class StudyTestRunner {
+ public static final String TEST_OK = "#study_plugin test OK";
+ private static final String TEST_FAILED = "#study_plugin FAILED + ";
+ private final Task myTask;
+ private final VirtualFile myTaskDir;
+
+ StudyTestRunner(Task task, VirtualFile taskDir) {
+ myTask = task;
+ myTaskDir = taskDir;
+ }
+
+ Process launchTests(Project project, String executablePath) throws ExecutionException {
+ Sdk sdk = PythonSdkType.findPythonSdk(ModuleManager.getInstance(project).getModules()[0]);
+ File testRunner = new File(myTaskDir.getPath(), myTask.getTestFile());
+ GeneralCommandLine commandLine = new GeneralCommandLine();
+ commandLine.setWorkDirectory(myTaskDir.getPath());
+ final Map<String, String> env = commandLine.getEnvironment();
+ final VirtualFile courseDir = project.getBaseDir();
+ if (courseDir != null)
+ env.put(PYTHONPATH, courseDir.getPath());
+ if (sdk != null) {
+ String pythonPath = sdk.getHomePath();
+ if (pythonPath != null) {
+ commandLine.setExePath(pythonPath);
+ commandLine.addParameter(testRunner.getPath());
+ final Course course = StudyTaskManager.getInstance(project).getCourse();
+ assert course != null;
+ commandLine.addParameter(new File(course.getResourcePath()).getParent());
+ commandLine.addParameter(FileUtil.toSystemDependentName(executablePath));
+ return commandLine.createProcess();
+ }
+ }
+ return null;
+ }
+
+
+ String getPassedTests(Process p) {
+ InputStream testOutput = p.getInputStream();
+ BufferedReader testOutputReader = new BufferedReader(new InputStreamReader(testOutput));
+ String line;
+ try {
+ while ((line = testOutputReader.readLine()) != null) {
+ if (line.contains(TEST_FAILED)) {
+ return line.substring(TEST_FAILED.length(), line.length());
+ }
+ }
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ finally {
+ StudyUtils.closeSilently(testOutputReader);
+ }
+ return TEST_OK;
+ }
+ }
+
+ public void check(@NotNull final Project project) {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() {
+ @Override
+ public void run() {
+ final Editor selectedEditor = StudyEditor.getSelectedEditor(project);
+ if (selectedEditor != null) {
+ final FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
+ final VirtualFile openedFile = fileDocumentManager.getFile(selectedEditor.getDocument());
+ if (openedFile != null) {
+ StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
+ final TaskFile selectedTaskFile = taskManager.getTaskFile(openedFile);
+ List<VirtualFile> filesToDelete = new ArrayList<VirtualFile>();
+ if (selectedTaskFile != null) {
+ final VirtualFile taskDir = openedFile.getParent();
+ Task currentTask = selectedTaskFile.getTask();
+ StudyStatus oldStatus = currentTask.getStatus();
+ Map<String, TaskFile> taskFiles = selectedTaskFile.getTask().getTaskFiles();
+ for (Map.Entry<String, TaskFile> entry : taskFiles.entrySet()) {
+ String name = entry.getKey();
+ TaskFile taskFile = entry.getValue();
+ VirtualFile virtualFile = taskDir.findChild(name);
+ if (virtualFile == null) {
+ continue;
+ }
+ VirtualFile windowFile = StudyUtils.flushWindows(FileDocumentManager.getInstance().getDocument(virtualFile), taskFile, virtualFile);
+ filesToDelete.add(windowFile);
+ FileDocumentManager.getInstance().saveAllDocuments();
+ }
+
+ StudyRunAction runAction = (StudyRunAction)ActionManager.getInstance().getAction(StudyRunAction.ACTION_ID);
+ if (runAction != null && currentTask.getTaskFiles().size() == 1) {
+ runAction.run(project);
+ }
+ final StudyTestRunner testRunner = new StudyTestRunner(currentTask, taskDir);
+ Process testProcess = null;
+ try {
+ testProcess = testRunner.launchTests(project, openedFile.getPath());
+ }
+ catch (ExecutionException e) {
+ LOG.error(e);
+ }
+ if (testProcess != null) {
+ String failedMessage = testRunner.getPassedTests(testProcess);
+ if (failedMessage.equals(StudyTestRunner.TEST_OK)) {
+ currentTask.setStatus(StudyStatus.Solved, oldStatus);
+ StudyUtils.updateStudyToolWindow(project);
+ selectedTaskFile.drawAllWindows(selectedEditor);
+ ProjectView.getInstance(project).refresh();
+ for (VirtualFile file:filesToDelete) {
+ try {
+ file.delete(this);
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ }
+ createTestResultPopUp("Congratulations!", JBColor.GREEN, project);
+ return;
+ }
+ for (Map.Entry<String, TaskFile> entry : taskFiles.entrySet()) {
+ String name = entry.getKey();
+ TaskFile taskFile = entry.getValue();
+ TaskFile answerTaskFile = new TaskFile();
+ VirtualFile virtualFile = taskDir.findChild(name);
+ if (virtualFile == null) {
+ continue;
+ }
+ VirtualFile answerFile = getCopyWithAnswers(taskDir, virtualFile, taskFile, answerTaskFile);
+ for (TaskWindow taskWindow : answerTaskFile.getTaskWindows()) {
+ Document document = FileDocumentManager.getInstance().getDocument(virtualFile);
+ if (document == null) {
+ continue;
+ }
+ if (!taskWindow.isValid(document)) {
+ continue;
+ }
+ check(project, taskWindow, answerFile, answerTaskFile, taskFile, document, testRunner, virtualFile);
+ }
+ FileEditor fileEditor = FileEditorManager.getInstance(project).getSelectedEditor(virtualFile);
+ Editor editor = null;
+ if (fileEditor instanceof StudyEditor) {
+ StudyEditor studyEditor = (StudyEditor) fileEditor;
+ editor = studyEditor.getEditor();
+ }
+
+ if (editor != null) {
+ taskFile.drawAllWindows(editor);
+ StudyUtils.synchronize();
+ }
+ try {
+ answerFile.delete(this);
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ }
+ for (VirtualFile file:filesToDelete) {
+ try {
+ file.delete(this);
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ }
+ currentTask.setStatus(StudyStatus.Failed, oldStatus);
+ StudyUtils.updateStudyToolWindow(project);
+ createTestResultPopUp(failedMessage, JBColor.RED, project);
+ }
+ }
+ }
+ }
+
+ }
+ });
+ }
+ });
+ }
+
+ private void check(Project project,
+ TaskWindow taskWindow,
+ VirtualFile answerFile,
+ TaskFile answerTaskFile,
+ TaskFile usersTaskFile,
+ Document usersDocument,
+ StudyTestRunner testRunner,
+ VirtualFile openedFile) {
+
+ try {
+ VirtualFile windowCopy = answerFile.copy(this, answerFile.getParent(), answerFile.getNameWithoutExtension() + "_window" + taskWindow.getIndex() + ".py");
+ final FileDocumentManager documentManager = FileDocumentManager.getInstance();
+ final Document windowDocument = documentManager.getDocument(windowCopy);
+ if (windowDocument != null) {
+ StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
+ Course course = taskManager.getCourse();
+ Task task = usersTaskFile.getTask();
+ int taskNum = task.getIndex() + 1;
+ int lessonNum = task.getLesson().getIndex() + 1;
+ assert course != null;
+ String pathToResource = FileUtil.join(new File(course.getResourcePath()).getParent(), Lesson.LESSON_DIR + lessonNum, Task.TASK_DIR + taskNum);
+ File resourceFile = new File(pathToResource, windowCopy.getName());
+ FileUtil.copy(new File(pathToResource, openedFile.getName()), resourceFile);
+ TaskFile windowTaskFile = new TaskFile();
+ TaskFile.copy(answerTaskFile, windowTaskFile);
+ StudyDocumentListener listener = new StudyDocumentListener(windowTaskFile);
+ windowDocument.addDocumentListener(listener);
+ int start = taskWindow.getRealStartOffset(windowDocument);
+ int end = start + taskWindow.getLength();
+ TaskWindow userTaskWindow = usersTaskFile.getTaskWindows().get(taskWindow.getIndex());
+ int userStart = userTaskWindow.getRealStartOffset(usersDocument);
+ int userEnd = userStart + userTaskWindow.getLength();
+ String text = usersDocument.getText(new TextRange(userStart, userEnd));
+ windowDocument.replaceString(start, end, text);
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ documentManager.saveDocument(windowDocument);
+ }
+ });
+ VirtualFile fileWindows = StudyUtils.flushWindows(windowDocument, windowTaskFile, windowCopy);
+ Process smartTestProcess = testRunner.launchTests(project, windowCopy.getPath());
+ boolean res = testRunner.getPassedTests(smartTestProcess).equals(StudyTestRunner.TEST_OK);
+ userTaskWindow.setStatus(res ? StudyStatus.Solved : StudyStatus.Failed, StudyStatus.Unchecked);
+ windowCopy.delete(this);
+ fileWindows.delete(this);
+ if (!resourceFile.delete()) {
+ LOG.error("failed to delete", resourceFile.getPath());
+ }
+ }
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ catch (ExecutionException e) {
+ LOG.error(e);
+ }
+ }
+
+
+ private VirtualFile getCopyWithAnswers(final VirtualFile taskDir,
+ final VirtualFile file,
+ final TaskFile source,
+ TaskFile target) {
+ VirtualFile copy = null;
+ try {
+
+ copy = file.copy(this, taskDir, file.getNameWithoutExtension() +"_answers.py");
+ final FileDocumentManager documentManager = FileDocumentManager.getInstance();
+ final Document document = documentManager.getDocument(copy);
+ if (document != null) {
+ TaskFile.copy(source, target);
+ StudyDocumentListener listener = new StudyDocumentListener(target);
+ document.addDocumentListener(listener);
+ for (TaskWindow taskWindow : target.getTaskWindows()) {
+ if (!taskWindow.isValid(document)) {
+ continue;
+ }
+ final int start = taskWindow.getRealStartOffset(document);
+ final int end = start + taskWindow.getLength();
+ final String text = taskWindow.getPossibleAnswer();
+ document.replaceString(start, end, text);
+ }
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ documentManager.saveDocument(document);
+ }
+ });
+ }
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+
+
+ return copy;
+ }
+
+ private static void createTestResultPopUp(final String text, Color color, @NotNull final Project project) {
+ BalloonBuilder balloonBuilder =
+ JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(text, null, color, null);
+ Balloon balloon = balloonBuilder.createBalloon();
+ StudyEditor studyEditor = StudyEditor.getSelectedStudyEditor(project);
+ assert studyEditor != null;
+ JButton checkButton = studyEditor.getCheckButton();
+ balloon.showInCenterOf(checkButton);
+ }
+
+ @Override
+ public void actionPerformed(AnActionEvent e) {
+ Project project = e.getProject();
+ if (project != null) {
+ check(project);
+ }
+ }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyEditInputAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyEditInputAction.java
new file mode 100644
index 000000000000..5b9a6fef23ac
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyEditInputAction.java
@@ -0,0 +1,213 @@
+package com.jetbrains.python.edu.actions;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.ide.ui.UISettings;
+import com.intellij.openapi.actionSystem.*;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.popup.JBPopup;
+import com.intellij.openapi.ui.popup.JBPopupAdapter;
+import com.intellij.openapi.ui.popup.JBPopupFactory;
+import com.intellij.openapi.ui.popup.LightweightWindowEvent;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.wm.IdeFocusManager;
+import com.intellij.ui.tabs.TabInfo;
+import com.intellij.ui.tabs.TabsListener;
+import com.intellij.ui.tabs.impl.JBEditorTabs;
+import com.jetbrains.python.edu.StudyTaskManager;
+import com.jetbrains.python.edu.StudyUtils;
+import com.jetbrains.python.edu.course.Task;
+import com.jetbrains.python.edu.course.TaskFile;
+import com.jetbrains.python.edu.course.UserTest;
+import com.jetbrains.python.edu.editor.StudyEditor;
+import com.jetbrains.python.edu.ui.StudyTestContentPanel;
+import icons.StudyIcons;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class StudyEditInputAction extends DumbAwareAction {
+
+ public static final String TEST_TAB_NAME = "test";
+ public static final String USER_TEST_INPUT = "input";
+ public static final String USER_TEST_OUTPUT = "output";
+ private static final Logger LOG = Logger.getInstance(StudyEditInputAction.class.getName());
+ private JBEditorTabs tabbedPane;
+ private Map<TabInfo, UserTest> myEditableTabs = new HashMap<TabInfo, UserTest>();
+
+ public void showInput(final Project project) {
+ final Editor selectedEditor = StudyEditor.getSelectedEditor(project);
+ if (selectedEditor != null) {
+ FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
+ final VirtualFile openedFile = fileDocumentManager.getFile(selectedEditor.getDocument());
+ StudyTaskManager studyTaskManager = StudyTaskManager.getInstance(project);
+ assert openedFile != null;
+ TaskFile taskFile = studyTaskManager.getTaskFile(openedFile);
+ assert taskFile != null;
+ final Task currentTask = taskFile.getTask();
+ tabbedPane = new JBEditorTabs(project, ActionManager.getInstance(), IdeFocusManager.findInstance(), project);
+ tabbedPane.addListener(new TabsListener.Adapter() {
+ @Override
+ public void selectionChanged(TabInfo oldSelection, TabInfo newSelection) {
+ if (newSelection.getIcon() != null) {
+ int tabCount = tabbedPane.getTabCount();
+ VirtualFile taskDir = openedFile.getParent();
+ VirtualFile testsDir = taskDir.findChild(Task.USER_TESTS);
+ assert testsDir != null;
+ UserTest userTest = createUserTest(testsDir, currentTask);
+ userTest.setEditable(true);
+ StudyTestContentPanel testContentPanel = new StudyTestContentPanel(userTest);
+ TabInfo testTab = addTestTab(tabbedPane.getTabCount(), testContentPanel, currentTask, true);
+ myEditableTabs.put(testTab, userTest);
+ tabbedPane.addTabSilently(testTab, tabCount - 1);
+ tabbedPane.select(testTab, true);
+ }
+ }
+ });
+ List<UserTest> userTests = currentTask.getUserTests();
+ int i = 1;
+ for (UserTest userTest : userTests) {
+ String inputFileText = StudyUtils.getFileText(null, userTest.getInput(), false);
+ String outputFileText = StudyUtils.getFileText(null, userTest.getOutput(), false);
+ StudyTestContentPanel myContentPanel = new StudyTestContentPanel(userTest);
+ myContentPanel.addInputContent(inputFileText);
+ myContentPanel.addOutputContent(outputFileText);
+ TabInfo testTab = addTestTab(i, myContentPanel, currentTask, userTest.isEditable());
+ tabbedPane.addTabSilently(testTab, i - 1);
+ if (userTest.isEditable()) {
+ myEditableTabs.put(testTab, userTest);
+ }
+ i++;
+ }
+ TabInfo plusTab = new TabInfo(new JPanel());
+ plusTab.setIcon(StudyIcons.Add);
+ tabbedPane.addTabSilently(plusTab, tabbedPane.getTabCount());
+ final JBPopup hint =
+ JBPopupFactory.getInstance().createComponentPopupBuilder(tabbedPane.getComponent(), tabbedPane.getComponent())
+ .setResizable(true)
+ .setMovable(true)
+ .setRequestFocus(true)
+ .createPopup();
+ StudyEditor selectedStudyEditor = StudyEditor.getSelectedStudyEditor(project);
+ assert selectedStudyEditor != null;
+ hint.showInCenterOf(selectedStudyEditor.getComponent());
+ hint.addListener(new HintClosedListener(currentTask));
+ }
+ }
+
+
+ private static void flushBuffer(@NotNull final StringBuilder buffer, @NotNull final File file) {
+ PrintWriter printWriter = null;
+ try {
+ printWriter = new PrintWriter(new FileOutputStream(file));
+ printWriter.print(buffer.toString());
+ }
+ catch (FileNotFoundException e) {
+ LOG.error(e);
+ }
+ finally {
+ StudyUtils.closeSilently(printWriter);
+ }
+ StudyUtils.synchronize();
+ }
+
+ private static UserTest createUserTest(@NotNull final VirtualFile testsDir, @NotNull final Task currentTask) {
+ UserTest userTest = new UserTest();
+ List<UserTest> userTests = currentTask.getUserTests();
+ int testNum = userTests.size() + 1;
+ String inputName = USER_TEST_INPUT + testNum;
+ File inputFile = new File(testsDir.getPath(), inputName);
+ String outputName = USER_TEST_OUTPUT + testNum;
+ File outputFile = new File(testsDir.getPath(), outputName);
+ userTest.setInput(inputFile.getPath());
+ userTest.setOutput(outputFile.getPath());
+ userTests.add(userTest);
+ return userTest;
+ }
+
+ private TabInfo addTestTab(int nameIndex, final StudyTestContentPanel contentPanel, @NotNull final Task currentTask, boolean toBeClosable) {
+ TabInfo testTab = toBeClosable ? createClosableTab(contentPanel, currentTask) : new TabInfo(contentPanel);
+ return testTab.setText(TEST_TAB_NAME + String.valueOf(nameIndex));
+ }
+
+ private TabInfo createClosableTab(StudyTestContentPanel contentPanel, Task currentTask) {
+ TabInfo closableTab = new TabInfo(contentPanel);
+ final DefaultActionGroup tabActions = new DefaultActionGroup();
+ tabActions.add(new CloseTab(closableTab, currentTask));
+ closableTab.setTabLabelActions(tabActions, ActionPlaces.EDITOR_TAB);
+ return closableTab;
+ }
+
+ public void actionPerformed(AnActionEvent e) {
+ showInput(e.getProject());
+ }
+
+ private class HintClosedListener extends JBPopupAdapter {
+ private final Task myTask;
+ private HintClosedListener(@NotNull final Task task) {
+ myTask = task;
+ }
+
+ @Override
+ public void onClosed(LightweightWindowEvent event) {
+ for (final UserTest userTest : myTask.getUserTests()) {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ if (userTest.isEditable()) {
+ File inputFile = new File(userTest.getInput());
+ File outputFile = new File(userTest.getOutput());
+ flushBuffer(userTest.getInputBuffer(), inputFile);
+ flushBuffer(userTest.getOutputBuffer(), outputFile);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ private class CloseTab extends AnAction implements DumbAware {
+
+ private final TabInfo myTabInfo;
+ private final Task myTask;
+
+ public CloseTab(final TabInfo info, @NotNull final Task task) {
+ myTabInfo = info;
+ myTask = task;
+ }
+
+ @Override
+ public void update(final AnActionEvent e) {
+ e.getPresentation().setIcon(tabbedPane.isEditorTabs() ? AllIcons.Actions.CloseNew : AllIcons.Actions.Close);
+ e.getPresentation().setHoveredIcon(tabbedPane.isEditorTabs() ? AllIcons.Actions.CloseNewHovered : AllIcons.Actions.CloseHovered);
+ e.getPresentation().setVisible(UISettings.getInstance().SHOW_CLOSE_BUTTON);
+ e.getPresentation().setText("Delete test");
+ }
+
+ @Override
+ public void actionPerformed(final AnActionEvent e) {
+ tabbedPane.removeTab(myTabInfo);
+ UserTest userTest = myEditableTabs.get(myTabInfo);
+ File testInputFile = new File(userTest.getInput());
+ File testOutputFile = new File(userTest.getOutput());
+ if (testInputFile.delete() && testOutputFile.delete()) {
+ StudyUtils.synchronize();
+ } else {
+ LOG.error("failed to delete user tests");
+ }
+ myTask.getUserTests().remove(userTest);
+ }
+ }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextStudyTaskAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextStudyTaskAction.java
new file mode 100644
index 000000000000..81818a95c044
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextStudyTaskAction.java
@@ -0,0 +1,24 @@
+package com.jetbrains.python.edu.actions;
+
+import com.jetbrains.python.edu.editor.StudyEditor;
+import com.jetbrains.python.edu.course.Task;
+
+import javax.swing.*;
+
+public class StudyNextStudyTaskAction extends StudyTaskNavigationAction {
+
+ @Override
+ protected JButton getButton(StudyEditor selectedStudyEditor) {
+ return selectedStudyEditor.getNextTaskButton();
+ }
+
+ @Override
+ protected String getNavigationFinishedMessage() {
+ return "It's the last task";
+ }
+
+ @Override
+ protected Task getTargetTask(Task sourceTask) {
+ return sourceTask.next();
+ }
+} \ No newline at end of file
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextWindowAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextWindowAction.java
new file mode 100644
index 000000000000..595aeeff42e3
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextWindowAction.java
@@ -0,0 +1,32 @@
+package com.jetbrains.python.edu.actions;
+
+import com.jetbrains.python.edu.StudyUtils;
+import com.jetbrains.python.edu.course.TaskWindow;
+import icons.StudyIcons;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * move caret to next task window
+ */
+public class StudyNextWindowAction extends StudyWindowNavigationAction {
+ public static final String ACTION_ID = "NextWindow";
+ public static final String SHORTCUT = "ctrl pressed PERIOD";
+ public static final String SHORTCUT2 = "ctrl pressed ENTER";
+
+ public StudyNextWindowAction() {
+ super("NextWindowAction", "Select next window", StudyIcons.Next);
+ }
+
+ @Override
+ protected TaskWindow getNextTaskWindow(@NotNull final TaskWindow window) {
+ int index = window.getIndex();
+ List<TaskWindow> windows = window.getTaskFile().getTaskWindows();
+ if (StudyUtils.indexIsValid(index, windows)) {
+ int newIndex = index + 1;
+ return newIndex == windows.size() ? windows.get(0) : windows.get(newIndex);
+ }
+ return null;
+ }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyPrevWindowAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyPrevWindowAction.java
new file mode 100644
index 000000000000..347456189a00
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyPrevWindowAction.java
@@ -0,0 +1,34 @@
+package com.jetbrains.python.edu.actions;
+
+import com.jetbrains.python.edu.StudyUtils;
+import com.jetbrains.python.edu.course.TaskWindow;
+import icons.StudyIcons;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+/**
+ * author: liana
+ * data: 6/30/14.
+ */
+public class StudyPrevWindowAction extends StudyWindowNavigationAction {
+ public static final String ACTION_ID = "PrevWindowAction";
+ public static final String SHORTCUT = "ctrl pressed COMMA";
+
+ public StudyPrevWindowAction() {
+ super("PrevWindowAction", "Select previous window", StudyIcons.Prev);
+ }
+
+
+ @Nullable
+ @Override
+ protected TaskWindow getNextTaskWindow(@NotNull final TaskWindow window) {
+ int prevIndex = window.getIndex() - 1;
+ List<TaskWindow> windows = window.getTaskFile().getTaskWindows();
+ if (StudyUtils.indexIsValid(prevIndex, windows)) {
+ return windows.get(prevIndex);
+ }
+ return null;
+ }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyPreviousStudyTaskAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyPreviousStudyTaskAction.java
new file mode 100644
index 000000000000..bc26c28cfabd
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyPreviousStudyTaskAction.java
@@ -0,0 +1,25 @@
+package com.jetbrains.python.edu.actions;
+
+
+import com.jetbrains.python.edu.editor.StudyEditor;
+import com.jetbrains.python.edu.course.Task;
+
+import javax.swing.*;
+
+public class StudyPreviousStudyTaskAction extends StudyTaskNavigationAction {
+
+ @Override
+ protected JButton getButton(StudyEditor selectedStudyEditor) {
+ return selectedStudyEditor.getPrevTaskButton();
+ }
+
+ @Override
+ protected String getNavigationFinishedMessage() {
+ return "It's already the first task";
+ }
+
+ @Override
+ protected Task getTargetTask(Task sourceTask) {
+ return sourceTask.prev();
+ }
+} \ No newline at end of file
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskAction.java
new file mode 100644
index 000000000000..f8abb0b63365
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskAction.java
@@ -0,0 +1,122 @@
+package com.jetbrains.python.edu.actions;
+
+import com.intellij.ide.projectView.ProjectView;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.MessageType;
+import com.intellij.openapi.ui.popup.Balloon;
+import com.intellij.openapi.ui.popup.BalloonBuilder;
+import com.intellij.openapi.ui.popup.JBPopupFactory;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.wm.IdeFocusManager;
+import com.jetbrains.python.edu.StudyDocumentListener;
+import com.jetbrains.python.edu.StudyTaskManager;
+import com.jetbrains.python.edu.StudyUtils;
+import com.jetbrains.python.edu.course.*;
+import com.jetbrains.python.edu.editor.StudyEditor;
+
+import java.io.*;
+
+public class StudyRefreshTaskAction extends DumbAwareAction {
+ private static final Logger LOG = Logger.getInstance(StudyRefreshTaskAction.class.getName());
+
+ public void refresh(final Project project) {
+ ApplicationManager.getApplication().invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
+ @Override
+ public void run() {
+ final Editor editor = StudyEditor.getSelectedEditor(project);
+ assert editor != null;
+ final Document document = editor.getDocument();
+ StudyDocumentListener listener = StudyEditor.getListener(document);
+ if (listener != null) {
+ document.removeDocumentListener(listener);
+ }
+ final int lineCount = document.getLineCount();
+ if (lineCount != 0) {
+ CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() {
+ @Override
+ public void run() {
+ document.deleteString(0, document.getLineEndOffset(lineCount - 1));
+ }
+ });
+ }
+ StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
+ Course course = taskManager.getCourse();
+ assert course != null;
+ File resourceFile = new File(course.getResourcePath());
+ File resourceRoot = resourceFile.getParentFile();
+ FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
+ VirtualFile openedFile = fileDocumentManager.getFile(document);
+ assert openedFile != null;
+ final TaskFile selectedTaskFile = taskManager.getTaskFile(openedFile);
+ assert selectedTaskFile != null;
+ Task currentTask = selectedTaskFile.getTask();
+ String lessonDir = Lesson.LESSON_DIR + String.valueOf(currentTask.getLesson().getIndex() + 1);
+ String taskDir = Task.TASK_DIR + String.valueOf(currentTask.getIndex() + 1);
+ File pattern = new File(new File(new File(resourceRoot, lessonDir), taskDir), openedFile.getName());
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(new FileInputStream(pattern)));
+ String line;
+ StringBuilder patternText = new StringBuilder();
+ while ((line = reader.readLine()) != null) {
+ patternText.append(line);
+ patternText.append("\n");
+ }
+ int patternLength = patternText.length();
+ if (patternText.charAt(patternLength - 1) == '\n') {
+ patternText.delete(patternLength - 1, patternLength);
+ }
+ document.setText(patternText);
+ StudyStatus oldStatus = currentTask.getStatus();
+ LessonInfo lessonInfo = currentTask.getLesson().getLessonInfo();
+ lessonInfo.update(oldStatus, -1);
+ lessonInfo.update(StudyStatus.Unchecked, +1);
+ StudyUtils.updateStudyToolWindow(project);
+ for (TaskWindow taskWindow : selectedTaskFile.getTaskWindows()) {
+ taskWindow.reset();
+ }
+ ProjectView.getInstance(project).refresh();
+ if (listener != null) {
+ document.addDocumentListener(listener);
+ }
+ selectedTaskFile.drawAllWindows(editor);
+ IdeFocusManager.getInstance(project).requestFocus(editor.getContentComponent(), true);
+ selectedTaskFile.navigateToFirstTaskWindow(editor);
+ BalloonBuilder balloonBuilder =
+ JBPopupFactory.getInstance().createHtmlTextBalloonBuilder("You can now start again", MessageType.INFO, null);
+ Balloon balloon = balloonBuilder.createBalloon();
+ StudyEditor selectedStudyEditor = StudyEditor.getSelectedStudyEditor(project);
+ assert selectedStudyEditor != null;
+ balloon.showInCenterOf(selectedStudyEditor.getRefreshButton());
+ }
+ catch (FileNotFoundException e1) {
+ LOG.error(e1);
+ }
+ catch (IOException e1) {
+ LOG.error(e1);
+ }
+ finally {
+ StudyUtils.closeSilently(reader);
+ }
+ }
+ });
+ }
+ });
+ }
+
+ public void actionPerformed(AnActionEvent e) {
+ refresh(e.getProject());
+ }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRunAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRunAction.java
new file mode 100644
index 000000000000..71e95defdedc
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRunAction.java
@@ -0,0 +1,89 @@
+package com.jetbrains.python.edu.actions;
+
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.RunContentExecutor;
+import com.intellij.execution.configurations.GeneralCommandLine;
+import com.intellij.execution.process.OSProcessHandler;
+import com.intellij.execution.process.ProcessHandler;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.python.sdk.PythonSdkType;
+import com.jetbrains.python.edu.StudyResourceManger;
+import com.jetbrains.python.edu.StudyTaskManager;
+import com.jetbrains.python.edu.course.Task;
+import com.jetbrains.python.edu.course.TaskFile;
+import com.jetbrains.python.edu.editor.StudyEditor;
+
+import java.io.File;
+
+public class StudyRunAction extends DumbAwareAction {
+ private static final Logger LOG = Logger.getInstance(StudyRunAction.class.getName());
+ public static final String ACTION_ID = "StudyRunAction";
+
+ public void run(Project project) {
+ Editor selectedEditor = StudyEditor.getSelectedEditor(project);
+ FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
+ assert selectedEditor != null;
+ VirtualFile openedFile = fileDocumentManager.getFile(selectedEditor.getDocument());
+ StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
+ if (openedFile != null && openedFile.getCanonicalPath() != null) {
+ String filePath = openedFile.getCanonicalPath();
+ GeneralCommandLine cmd = new GeneralCommandLine();
+ cmd.setWorkDirectory(openedFile.getParent().getCanonicalPath());
+ Sdk sdk = PythonSdkType.findPythonSdk(ModuleManager.getInstance(project).getModules()[0]);
+ if (sdk != null) {
+ String pythonPath = sdk.getHomePath();
+ if (pythonPath != null) {
+ cmd.setExePath(pythonPath);
+ TaskFile selectedTaskFile = taskManager.getTaskFile(openedFile);
+ assert selectedTaskFile != null;
+ Task currentTask = selectedTaskFile.getTask();
+ if (!currentTask.getUserTests().isEmpty()) {
+ cmd.addParameter(new File(project.getBaseDir().getPath(), StudyResourceManger.USER_TESTER).getPath());
+ cmd.addParameter(pythonPath);
+ cmd.addParameter(filePath);
+ Process p;
+ try {
+ p = cmd.createProcess();
+ }
+ catch (ExecutionException e) {
+ LOG.error(e);
+ return;
+ }
+ ProcessHandler handler = new OSProcessHandler(p);
+
+ RunContentExecutor executor = new RunContentExecutor(project, handler);
+ Disposer.register(project, executor);
+ executor.run();
+ return;
+ }
+ try {
+ cmd.addParameter(filePath);
+ Process p = cmd.createProcess();
+ ProcessHandler handler = new OSProcessHandler(p);
+
+ RunContentExecutor executor = new RunContentExecutor(project, handler);
+ Disposer.register(project, executor);
+ executor.run();
+ }
+
+ catch (ExecutionException e) {
+ LOG.error(e);
+ }
+ }
+ }
+ }
+ }
+
+ public void actionPerformed(AnActionEvent e) {
+ run(e.getProject());
+ }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyShowHintAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyShowHintAction.java
new file mode 100644
index 000000000000..1efa90889449
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyShowHintAction.java
@@ -0,0 +1,95 @@
+package com.jetbrains.python.edu.actions;
+
+import com.intellij.codeInsight.documentation.DocumentationComponent;
+import com.intellij.codeInsight.documentation.DocumentationManager;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.LogicalPosition;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.popup.JBPopup;
+import com.intellij.openapi.ui.popup.JBPopupFactory;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.jetbrains.python.edu.StudyTaskManager;
+import com.jetbrains.python.edu.StudyUtils;
+import com.jetbrains.python.edu.course.Course;
+import com.jetbrains.python.edu.course.TaskFile;
+import com.jetbrains.python.edu.course.TaskWindow;
+import com.jetbrains.python.edu.editor.StudyEditor;
+import icons.StudyIcons;
+
+import java.io.File;
+
+public class StudyShowHintAction extends DumbAwareAction {
+ public static final String ACTION_ID = "ShowHintAction";
+ public static final String SHORTCUT = "ctrl pressed 7";
+
+ public StudyShowHintAction() {
+ super("Show hint", "Show hint", StudyIcons.ShowHint);
+ }
+
+ public void actionPerformed(AnActionEvent e) {
+ Project project = e.getProject();
+ if (project != null) {
+ DocumentationManager documentationManager = DocumentationManager.getInstance(project);
+ DocumentationComponent component = new DocumentationComponent(documentationManager);
+ Editor selectedEditor = StudyEditor.getSelectedEditor(project);
+ FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
+ assert selectedEditor != null;
+ VirtualFile openedFile = fileDocumentManager.getFile(selectedEditor.getDocument());
+ if (openedFile != null) {
+ StudyTaskManager taskManager = StudyTaskManager.getInstance(e.getProject());
+ TaskFile taskFile = taskManager.getTaskFile(openedFile);
+ if (taskFile != null) {
+ PsiFile file = PsiManager.getInstance(project).findFile(openedFile);
+ if (file != null) {
+ LogicalPosition pos = selectedEditor.getCaretModel().getLogicalPosition();
+ TaskWindow taskWindow = taskFile.getTaskWindow(selectedEditor.getDocument(), pos);
+ if (taskWindow != null) {
+ String hint = taskWindow.getHint();
+ if (hint == null) {
+ return;
+ }
+ Course course = taskManager.getCourse();
+ if (course != null) {
+ File resourceFile = new File(course.getResourcePath());
+ File resourceRoot = resourceFile.getParentFile();
+ if (resourceRoot != null && resourceRoot.exists()) {
+ File hintsDir = new File(resourceRoot, Course.HINTS_DIR);
+ if (hintsDir.exists()) {
+ String hintText = StudyUtils.getFileText(hintsDir.getAbsolutePath(), hint, true);
+ if (hintText != null) {
+ int offset = selectedEditor.getDocument().getLineStartOffset(pos.line) + pos.column;
+ PsiElement element = file.findElementAt(offset);
+ if (element != null) {
+ component.setData(element, hintText, true, null);
+ final JBPopup popup =
+ JBPopupFactory.getInstance().createComponentPopupBuilder(component, component)
+ .setDimensionServiceKey(project, DocumentationManager.JAVADOC_LOCATION_AND_SIZE, false)
+ .setResizable(true)
+ .setMovable(true)
+ .setRequestFocus(true)
+ .createPopup();
+ component.setHint(popup);
+ popup.showInBestPositionFor(selectedEditor);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void update(AnActionEvent e) {
+ StudyUtils.updateAction(e);
+ }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyTaskNavigationAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyTaskNavigationAction.java
new file mode 100644
index 000000000000..b781e7da8849
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyTaskNavigationAction.java
@@ -0,0 +1,97 @@
+package com.jetbrains.python.edu.actions;
+
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.MessageType;
+import com.intellij.openapi.ui.popup.Balloon;
+import com.intellij.openapi.ui.popup.BalloonBuilder;
+import com.intellij.openapi.ui.popup.JBPopupFactory;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.python.edu.StudyTaskManager;
+import com.jetbrains.python.edu.course.Lesson;
+import com.jetbrains.python.edu.course.Task;
+import com.jetbrains.python.edu.course.TaskFile;
+import com.jetbrains.python.edu.editor.StudyEditor;
+
+import javax.swing.*;
+import java.util.Map;
+
+/**
+ * author: liana
+ * data: 7/21/14.
+ */
+abstract public class StudyTaskNavigationAction extends DumbAwareAction {
+ public void navigateTask(Project project) {
+ Editor selectedEditor = StudyEditor.getSelectedEditor(project);
+ FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
+ assert selectedEditor != null;
+ VirtualFile openedFile = fileDocumentManager.getFile(selectedEditor.getDocument());
+ StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
+ assert openedFile != null;
+ TaskFile selectedTaskFile = taskManager.getTaskFile(openedFile);
+ assert selectedTaskFile != null;
+ Task currentTask = selectedTaskFile.getTask();
+ Task nextTask = getTargetTask(currentTask);
+ if (nextTask == null) {
+ BalloonBuilder balloonBuilder =
+ JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(getNavigationFinishedMessage(), MessageType.INFO, null);
+ Balloon balloon = balloonBuilder.createBalloon();
+ StudyEditor selectedStudyEditor = StudyEditor.getSelectedStudyEditor(project);
+ balloon.showInCenterOf(getButton(selectedStudyEditor));
+ return;
+ }
+ for (VirtualFile file : FileEditorManager.getInstance(project).getOpenFiles()) {
+ FileEditorManager.getInstance(project).closeFile(file);
+ }
+ int nextTaskIndex = nextTask.getIndex();
+ int lessonIndex = nextTask.getLesson().getIndex();
+ Map<String, TaskFile> nextTaskFiles = nextTask.getTaskFiles();
+ if (nextTaskFiles.isEmpty()) {
+ return;
+ }
+ VirtualFile projectDir = project.getBaseDir();
+ String lessonDirName = Lesson.LESSON_DIR + String.valueOf(lessonIndex + 1);
+ if (projectDir == null) {
+ return;
+ }
+ VirtualFile lessonDir = projectDir.findChild(lessonDirName);
+ if (lessonDir == null) {
+ return;
+ }
+ String taskDirName = Task.TASK_DIR + String.valueOf(nextTaskIndex + 1);
+ VirtualFile taskDir = lessonDir.findChild(taskDirName);
+ if (taskDir == null) {
+ return;
+ }
+ VirtualFile shouldBeActive = null;
+ for (Map.Entry<String, TaskFile> entry : nextTaskFiles.entrySet()) {
+ String name = entry.getKey();
+ TaskFile taskFile = entry.getValue();
+ VirtualFile vf = taskDir.findChild(name);
+ if (vf != null) {
+ FileEditorManager.getInstance(project).openFile(vf, true);
+ if (!taskFile.getTaskWindows().isEmpty()) {
+ shouldBeActive = vf;
+ }
+ }
+ }
+ if (shouldBeActive != null) {
+ FileEditorManager.getInstance(project).openFile(shouldBeActive, true);
+ }
+ }
+
+ protected abstract JButton getButton(StudyEditor selectedStudyEditor);
+
+ @Override
+ public void actionPerformed(AnActionEvent e) {
+ navigateTask(e.getProject());
+ }
+
+ protected abstract String getNavigationFinishedMessage();
+
+ protected abstract Task getTargetTask(Task sourceTask);
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyWindowNavigationAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyWindowNavigationAction.java
new file mode 100644
index 000000000000..8c6b90221555
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyWindowNavigationAction.java
@@ -0,0 +1,65 @@
+package com.jetbrains.python.edu.actions;
+
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.python.edu.StudyTaskManager;
+import com.jetbrains.python.edu.StudyUtils;
+import com.jetbrains.python.edu.course.TaskFile;
+import com.jetbrains.python.edu.course.TaskWindow;
+import com.jetbrains.python.edu.editor.StudyEditor;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+
+abstract public class StudyWindowNavigationAction extends DumbAwareAction {
+
+ public StudyWindowNavigationAction(String actionId, String description, Icon icon) {
+ super(actionId, description, icon);
+ }
+
+ public void navigateWindow(@NotNull final Project project) {
+ Editor selectedEditor = StudyEditor.getSelectedEditor(project);
+ if (selectedEditor != null) {
+ FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
+ VirtualFile openedFile = fileDocumentManager.getFile(selectedEditor.getDocument());
+ if (openedFile != null) {
+ StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
+ TaskFile selectedTaskFile = taskManager.getTaskFile(openedFile);
+ if (selectedTaskFile != null) {
+ TaskWindow selectedTaskWindow = selectedTaskFile.getSelectedTaskWindow();
+ if (selectedTaskWindow == null) {
+ return;
+ }
+ TaskWindow nextTaskWindow = getNextTaskWindow(selectedTaskWindow);
+ if (nextTaskWindow == null) {
+ return;
+ }
+ nextTaskWindow.draw(selectedEditor, true, true);
+ selectedTaskFile.setSelectedTaskWindow(nextTaskWindow);
+ }
+ }
+ }
+ }
+
+ @Nullable
+ protected abstract TaskWindow getNextTaskWindow(@NotNull final TaskWindow window);
+
+ @Override
+ public void actionPerformed(AnActionEvent e) {
+ Project project = e.getProject();
+ if (project == null) {
+ return;
+ }
+ navigateWindow(project);
+ }
+
+ @Override
+ public void update(AnActionEvent e) {
+ StudyUtils.updateAction(e);
+ }
+}