diff options
Diffstat (limited to 'python/edu/learn-python/src/com/jetbrains/python/edu/actions')
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); + } +} |