import com.intellij.ide.fileTemplates.FileTemplateUtil;
import com.intellij.ide.util.DirectoryUtil;
import com.intellij.openapi.command.WriteCommandAction;
+import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.Project;
public class PyCCProjectGenerator extends PythonProjectGenerator implements DirectoryProjectGenerator {
+ private static final Logger LOG = Logger.getInstance(PyCCProjectGenerator.class);
private CCNewProjectPanel mySettingsPanel;
@Nls
catch (Exception ignored) {
}
DirectoryUtil.createSubdirectories(EduNames.HINTS, projectDir, "\\/");
- final PsiDirectory lessonDir = CCCreateLesson.createLessonDir(project, 1, null, null);
+ PsiDirectory lessonDir = CCCreateLesson.createLesson(null, project, baseDir, course);
+ if (lessonDir == null) {
+ LOG.error("Failed to create lesson");
+ return;
+ }
CCCreateTask.createTask(null, project, lessonDir, false);
}
}.execute();
<orderEntry type="library" name="gson" level="project" />
<orderEntry type="module" module-name="lang-impl" />
<orderEntry type="module" module-name="educational" />
+ <orderEntry type="library" name="guava-tools" level="project" />
</component>
</module>
\ No newline at end of file
public StudyOrderable fun(VirtualFile file) {
return course.getLesson(file.getName());
}
- }, removedLesson, EduNames.LESSON);
+ }, removedLesson.getIndex(), EduNames.LESSON, -1);
course.getLessons().remove(removedLesson);
}
public StudyOrderable fun(VirtualFile file) {
return lesson.getTask(file.getName());
}
- }, task, EduNames.TASK);
+ }, task.getIndex(), EduNames.TASK, -1);
ModifiableRootModel model = EduUtils.getModel(lessonDir, project);
if (model == null) {
return;
package com.jetbrains.edu.coursecreator;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
import com.intellij.lang.Language;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.util.Function;
+import com.jetbrains.edu.EduUtils;
import com.jetbrains.edu.courseFormat.Course;
import com.jetbrains.edu.courseFormat.StudyOrderable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
+import java.util.*;
public class CCUtils {
private static final Logger LOG = Logger.getInstance(CCUtils.class);
@Nullable
public static CCLanguageManager getStudyLanguageManager(@NotNull final Course course) {
Language language = Language.findLanguageByID(course.getLanguage());
- return language == null ? null : CCLanguageManager.INSTANCE.forLanguage(language);
+ return language == null ? null : CCLanguageManager.INSTANCE.forLanguage(language);
}
public static boolean isAnswerFile(PsiElement element) {
/**
* This method decreases index and updates directory names of
* all tasks/lessons that have higher index than specified object
- * @param dirs directories that are used to get tasks/lessons
+ *
+ * @param dirs directories that are used to get tasks/lessons
* @param getStudyOrderable function that is used to get task/lesson from VirtualFile. This function can return null
- * @param thresholdObject task/lesson which index is used as threshold
- * @param prefix task or lesson directory name prefix
+ * @param threshold index is used as threshold
+ * @param prefix task or lesson directory name prefix
*/
public static void updateHigherElements(VirtualFile[] dirs,
- @NotNull Function<VirtualFile, StudyOrderable> getStudyOrderable,
- @NotNull final StudyOrderable thresholdObject,
- final String prefix) {
- int threshold = thresholdObject.getIndex();
- for (final VirtualFile dir : dirs) {
- final StudyOrderable orderable = getStudyOrderable.fun(dir);
- if (orderable == null) {
- continue;
- }
- int index = orderable.getIndex();
- if (index > threshold) {
- final int newIndex = index - 1;
- orderable.setIndex(newIndex);
- ApplicationManager.getApplication().runWriteAction(new Runnable() {
- @Override
- public void run() {
- try {
- dir.rename(this, prefix + newIndex);
- }
- catch (IOException e) {
- LOG.error(e);
- }
+ @NotNull final Function<VirtualFile, StudyOrderable> getStudyOrderable,
+ final int threshold,
+ final String prefix,
+ final int delta) {
+ ArrayList<VirtualFile> dirsToRename = new ArrayList<VirtualFile>
+ (Collections2.filter(Arrays.asList(dirs), new Predicate<VirtualFile>() {
+ @Override
+ public boolean apply(VirtualFile dir) {
+ final StudyOrderable orderable = getStudyOrderable.fun(dir);
+ if (orderable == null) {
+ return false;
}
- });
+ int index = orderable.getIndex();
+ return index > threshold;
+ }
+ }));
+ Collections.sort(dirsToRename, new Comparator<VirtualFile>() {
+ @Override
+ public int compare(VirtualFile o1, VirtualFile o2) {
+ StudyOrderable orderable1 = getStudyOrderable.fun(o1);
+ StudyOrderable orderable2 = getStudyOrderable.fun(o2);
+ //if we delete some dir we should start increasing numbers in dir names from the end
+ return (-delta) * EduUtils.INDEX_COMPARATOR.compare(orderable1, orderable2);
}
+ });
+
+ for (final VirtualFile dir : dirsToRename) {
+ final StudyOrderable orderable = getStudyOrderable.fun(dir);
+ final int newIndex = orderable.getIndex() + delta;
+ orderable.setIndex(newIndex);
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ dir.rename(this, prefix + newIndex);
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ }
+ });
}
}
}
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.LangDataKeys;
import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiManager;
+import com.intellij.util.Function;
import com.intellij.util.PlatformIcons;
import com.jetbrains.edu.EduNames;
import com.jetbrains.edu.EduUtils;
import com.jetbrains.edu.courseFormat.Course;
import com.jetbrains.edu.courseFormat.Lesson;
+import com.jetbrains.edu.courseFormat.StudyOrderable;
import com.jetbrains.edu.coursecreator.CCProjectService;
+import com.jetbrains.edu.coursecreator.CCUtils;
+import com.jetbrains.edu.coursecreator.ui.CCCreateStudyItemDialog;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import java.io.IOException;
-import java.util.List;
+import java.util.Collections;
public class CCCreateLesson extends DumbAwareAction {
- private static final Logger LOG = Logger.getInstance(CCCreateLesson.class.getName());
+ public static final String TITLE = "Create New " + EduNames.LESSON_TITLED;
public CCCreateLesson() {
- super("Lesson", "Create new Lesson", PlatformIcons.DIRECTORY_CLOSED_ICON);
+ super(EduNames.LESSON_TITLED, TITLE, PlatformIcons.DIRECTORY_CLOSED_ICON);
}
@Override
if (course == null) {
return;
}
- //"Create Lesson" invoked from project root creates new lesson as last lesson
- if (directory.getVirtualFile().equals(project.getBaseDir())) {
- final int size = course.getLessons().size();
- createLesson(project, size + 1, view);
- return;
- }
- //"Create Lesson" invoked from any of lesson directories creates new lesson as next lesson
- Lesson lesson = course.getLesson(directory.getName());
- if (lesson != null) {
- int index = lesson.getIndex();
- List<Lesson> lessons = course.getLessons();
- int lessonNum = lessons.size();
- for (int i = lessonNum; i >= index + 1; i--) {
- updateLesson(project, EduNames.LESSON, i);
- }
- final PsiDirectory parent = directory.getParent();
- if (parent == null) {
- return;
- }
- createLesson(project, index + 1, view);
- }
+ createLesson(view, project, directory.getVirtualFile(), course);
}
- private static void createLesson(@NotNull final Project project,
- final int index,
- final IdeView view) {
- final String lessonName = Messages.showInputDialog("Name:", "Lesson Name", null, EduNames.LESSON + index, null);
- if (lessonName == null) {
- return;
+ @Nullable
+ public static PsiDirectory createLesson(@Nullable IdeView view, @NotNull final Project project,
+ @NotNull final VirtualFile directory, @NotNull final Course course) {
+ Lesson lesson = getLesson(directory, project, course, view);
+ if (lesson == null) {
+ return null;
}
- ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ VirtualFile courseDir = project.getBaseDir();
+ int lessonIndex = lesson.getIndex();
+ CCUtils.updateHigherElements(courseDir.getChildren(), new Function<VirtualFile, StudyOrderable>() {
@Override
- public void run() {
- createLessonDir(project, index, lessonName, view);
+ public StudyOrderable fun(VirtualFile file) {
+ return course.getLesson(file.getName());
}
- });
+ }, lessonIndex - 1, EduNames.LESSON, 1);
+ course.addLesson(lesson);
+ Collections.sort(course.getLessons(), EduUtils.INDEX_COMPARATOR);
+ return createLessonDir(project, lessonIndex, view);
}
- private void updateLesson(@NotNull final Project project, final String lessonDirName, int i) {
- final VirtualFile lessonDir = project.getBaseDir().findChild(lessonDirName + i);
- if (lessonDir == null) {
- return;
+ @Nullable
+ private static Lesson getLesson(@NotNull final VirtualFile sourceDirectory,
+ @NotNull final Project project,
+ @NotNull final Course course,
+ @Nullable IdeView view) {
+
+ VirtualFile courseDir = project.getBaseDir();
+ String lessonName;
+ int lessonIndex;
+ if (sourceDirectory.equals(courseDir)) {
+ lessonIndex = course.getLessons().size() + 1;
+ String suggestedName = EduNames.LESSON + lessonIndex;
+ lessonName = view == null ? suggestedName : Messages.showInputDialog("Name:", TITLE, null, suggestedName, null);
+ } else {
+ Lesson sourceLesson = course.getLesson(sourceDirectory.getName());
+ if (sourceLesson == null) {
+ return null;
+ }
+ final int index = sourceLesson.getIndex();
+ CCCreateStudyItemDialog dialog = new CCCreateStudyItemDialog(project, EduNames.LESSON, sourceLesson.getName(), index);
+ dialog.show();
+ if (dialog.getExitCode() != DialogWrapper.OK_EXIT_CODE) {
+ return null;
+ }
+ lessonName = dialog.getName();
+ lessonIndex = index + dialog.getIndexDelta();
}
- final CCProjectService service = CCProjectService.getInstance(project);
- Lesson l = service.getCourse().getLesson(lessonDir.getName());
- if (l == null) {
- return;
+ if (lessonName == null) {
+ return null;
}
- l.setIndex(l.getIndex() + 1);
- final int next = i + 1;
+ return createAndInitLesson(course, lessonName, lessonIndex);
+ }
+
+ @NotNull
+ private static Lesson createAndInitLesson(@NotNull final Course course, @NotNull String lessonName, int lessonIndex) {
+ Lesson lesson = new Lesson();
+ lesson.setName(lessonName);
+ lesson.setCourse(course);
+ lesson.setIndex(lessonIndex);
+ return lesson;
+ }
+
+ @Nullable
+ public static PsiDirectory createLessonDir(@NotNull final Project project, final int index, final IdeView view) {
+ final PsiDirectory projectDir = PsiManager.getInstance(project).findDirectory(project.getBaseDir());
+ final PsiDirectory[] lessonDirectory = new PsiDirectory[1];
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
- try {
- lessonDir.rename(this, lessonDirName + next);
- }
- catch (IOException e1) {
- LOG.error(e1);
- }
+ lessonDirectory[0] = DirectoryUtil.createSubdirectories(EduNames.LESSON + index, projectDir, "\\/");
}
});
- }
-
- @Nullable
- public static PsiDirectory createLessonDir(@NotNull final Project project, int index, String name, final IdeView view) {
- String lessonFolderName = EduNames.LESSON + index;
- final PsiDirectory projectDir = PsiManager.getInstance(project).findDirectory(project.getBaseDir());
- final PsiDirectory lessonDirectory = DirectoryUtil.createSubdirectories(EduNames.LESSON + index, projectDir, "\\/");
- final CCProjectService service = CCProjectService.getInstance(project);
- if (lessonDirectory != null) {
+ if (lessonDirectory[0] != null) {
if (view != null) {
- view.selectElement(lessonDirectory);
+ view.selectElement(lessonDirectory[0]);
}
- final Lesson lesson = new Lesson();
- lesson.setName(name != null ? name : lessonFolderName);
- lesson.setIndex(index);
- service.getCourse().addLesson(lesson);
}
- return lessonDirectory;
+ return lessonDirectory[0];
}
@Override
}
final CCProjectService service = CCProjectService.getInstance(project);
Course course = service.getCourse();
-
- if (directory != null && course != null && project.getBaseDir().equals(directory)) {
- EduUtils.enableAction(event, false);
+ if (directory != null && course != null && course.getLesson(directory.getName()) != null) {
+ EduUtils.enableAction(event, true);
+ return;
}
EduUtils.enableAction(event, false);
}
--- /dev/null
+package com.jetbrains.edu.coursecreator.ui;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.util.text.StringUtil;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+
+public class CCCreateStudyItemDialog extends DialogWrapper {
+ private final CCCreateStudyItemPanel myPanel;
+
+ public CCCreateStudyItemDialog(@Nullable Project project, String itemName, String thresholdName, int thresholdIndex) {
+ super(project);
+ myPanel = new CCCreateStudyItemPanel(itemName, thresholdName, thresholdIndex);
+ setTitle("Create New " + StringUtil.toTitleCase(itemName));
+ init();
+ }
+
+ @Nullable
+ @Override
+ protected JComponent createCenterPanel() {
+ return myPanel;
+ }
+
+ public String getName() {
+ return myPanel.getItemName();
+ }
+
+ public int getIndexDelta() {
+ return myPanel.getIndexDelta();
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.jetbrains.edu.coursecreator.ui.CCCreateStudyItemPanel">
+ <grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <xy x="20" y="20" width="500" height="400"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <component id="608ec" class="javax.swing.JTextField" binding="myNameField">
+ <constraints>
+ <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="4" anchor="0" fill="1" indent="0" use-parent-layout="false">
+ <minimum-size width="150" height="-1"/>
+ <preferred-size width="300" height="-1"/>
+ </grid>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="fdff2" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="Name:"/>
+ </properties>
+ </component>
+ <vspacer id="e16c7">
+ <constraints>
+ <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false">
+ <preferred-size width="-1" height="10"/>
+ </grid>
+ </constraints>
+ </vspacer>
+ <nested-form id="92938" form-file="com/jetbrains/edu/coursecreator/ui/CCItemPositionPanel.form" binding="myPositionalPanel" custom-create="true">
+ <constraints>
+ <grid row="2" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="6" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ </nested-form>
+ </children>
+ </grid>
+</form>
--- /dev/null
+package com.jetbrains.edu.coursecreator.ui;
+
+import javax.swing.*;
+import java.awt.*;
+
+public class CCCreateStudyItemPanel extends JPanel {
+ private final String myItemName;
+ private JPanel myPanel;
+ private JTextField myNameField;
+ private CCItemPositionPanel myPositionalPanel;
+ private String myThresholdName;
+ private int myThresholdIndex;
+
+ public CCCreateStudyItemPanel(String itemName, String thresholdName, int thresholdIndex) {
+ myThresholdName = thresholdName;
+ myThresholdIndex = thresholdIndex;
+ myItemName = itemName;
+ myNameField.setText(itemName + thresholdIndex);
+ add(myPanel, BorderLayout.CENTER);
+ }
+
+ private void createUIComponents() {
+ myPositionalPanel = new CCItemPositionPanel(myItemName, myThresholdName, myThresholdIndex);
+ }
+
+ public String getItemName() {
+ return myNameField.getText();
+ }
+
+ public int getIndexDelta() {
+ return myPositionalPanel.getIndexDelta();
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.jetbrains.edu.coursecreator.ui.CCItemPositionPanel">
+ <grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <xy x="20" y="20" width="500" height="400"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <component id="e37cf" class="com.intellij.ui.components.JBRadioButton" binding="myBeforeButton" default-binding="true">
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="before"/>
+ </properties>
+ </component>
+ <component id="57593" class="com.intellij.ui.components.JBRadioButton" binding="myAfterButton" default-binding="true">
+ <constraints>
+ <grid row="2" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="after"/>
+ </properties>
+ </component>
+ <component id="cedb7" class="com.intellij.ui.components.JBLabel" binding="mySpecifyPositionLabel">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value=""/>
+ </properties>
+ </component>
+ </children>
+ </grid>
+</form>
--- /dev/null
+package com.jetbrains.edu.coursecreator.ui;
+
+import com.intellij.ui.components.JBLabel;
+import com.intellij.ui.components.JBRadioButton;
+
+import javax.swing.*;
+import java.awt.*;
+
+public class CCItemPositionPanel extends JPanel {
+ private JPanel myPanel;
+ private JBRadioButton myBeforeButton;
+ private JBRadioButton myAfterButton;
+ private JBLabel mySpecifyPositionLabel;
+
+ public CCItemPositionPanel(String itemName, String thresholdName, int thresholdNum) {
+ this.add(myPanel, BorderLayout.CENTER);
+ mySpecifyPositionLabel.setText("Specify " + itemName + " position:");
+ String postfix = itemName + " " + thresholdNum + " '" + thresholdName + "'";
+ ButtonGroup group = new ButtonGroup();
+ group.add(myBeforeButton);
+ group.add(myAfterButton);
+ myBeforeButton.setText("before " + postfix);
+ myAfterButton.setText("after " + postfix);
+ myBeforeButton.setSelected(true);
+ }
+
+ public int getIndexDelta() {
+ return myBeforeButton.isSelected() ? 0 : 1;
+ }
+}
*/
package com.jetbrains.edu;
+import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NonNls;
@NonNls
public static final String TASK_HTML = "task.html";
public static final String HINTS = "hints";
public static final String LESSON = "lesson";
+ public static final String LESSON_TITLED = StringUtil.toTitleCase(LESSON);
public static final String COURSE = "course";
public static final String TEST_TAB_NAME = "test";
public static final String USER_TEST_INPUT = "input";
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.jetbrains.edu.courseFormat.AnswerPlaceholder;
+import com.jetbrains.edu.courseFormat.StudyOrderable;
import com.jetbrains.edu.courseFormat.Task;
import com.jetbrains.edu.courseFormat.TaskFile;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
+import java.util.Comparator;
import java.util.Map;
public class EduUtils {
}
private static final Logger LOG = Logger.getInstance(EduUtils.class.getName());
+ public static Comparator<StudyOrderable> INDEX_COMPARATOR = new Comparator<StudyOrderable>() {
+ @Override
+ public int compare(StudyOrderable o1, StudyOrderable o2) {
+ return o1.getIndex() - o2.getIndex();
+ }
+ };
+
public static void commitAndSaveModel(final ModifiableRootModel model) {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
* @return index of object
*/
public static int getIndex(@NotNull final String fullName, @NotNull final String logicalName) {
- if (!fullName.contains(logicalName)) {
+ if (!fullName.startsWith(logicalName)) {
+ return -1;
+ }
+ try {
+ return Integer.parseInt(fullName.substring(logicalName.length())) - 1;
+ } catch(NumberFormatException e) {
return -1;
}
- return Integer.parseInt(fullName.substring(logicalName.length())) - 1;
}
public static boolean indexIsValid(int index, Collection collection) {