<errorHandler implementation="com.intellij.diagnostic.ITNReporter"/>
<renameHandler implementation="com.jetbrains.edu.coursecreator.CCRenameHandler"/>
<renameInputValidator implementation="com.jetbrains.edu.coursecreator.CCRenameInputValidator"/>
+ <refactoring.moveHandler implementation="com.jetbrains.edu.coursecreator.CCLessonMoveHandlerDelegate" order="first"/>
+ <refactoring.moveHandler implementation="com.jetbrains.edu.coursecreator.CCTaskMoveHandlerDelegate" order="first"/>
</extensions>
<application-components>
--- /dev/null
+package com.jetbrains.edu.coursecreator;
+
+import com.intellij.ide.IdeView;
+import com.intellij.ide.util.DirectoryChooserUtil;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.LangDataKeys;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Editor;
+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.PsiElement;
+import com.intellij.psi.PsiReference;
+import com.intellij.refactoring.move.MoveCallback;
+import com.intellij.refactoring.move.MoveHandlerDelegate;
+import com.intellij.util.Function;
+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.StudyItem;
+import com.jetbrains.edu.coursecreator.ui.CCMoveStudyItemDialog;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.util.Collections;
+
+public class CCLessonMoveHandlerDelegate extends MoveHandlerDelegate {
+
+ private static final Logger LOG = Logger.getInstance(CCLessonMoveHandlerDelegate.class);
+
+ @Override
+ public boolean canMove(DataContext dataContext) {
+ if (CommonDataKeys.PSI_FILE.getData(dataContext) != null) {
+ return false;
+ }
+ IdeView view = LangDataKeys.IDE_VIEW.getData(dataContext);
+ if (view == null) {
+ return false;
+ }
+ PsiDirectory sourceDirectory = DirectoryChooserUtil.getOrChooseDirectory(view);
+ return CCUtils.isLessonDir(sourceDirectory);
+ }
+
+ @Override
+ public boolean canMove(PsiElement[] elements, @Nullable PsiElement targetContainer) {
+ if (elements.length > 0 && elements[0] instanceof PsiDirectory) {
+ return CCUtils.isLessonDir(((PsiDirectory)elements[0]));
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isValidTarget(PsiElement psiElement, PsiElement[] sources) {
+ return true;
+ }
+
+ @Override
+ public void doMove(final Project project,
+ PsiElement[] elements,
+ @Nullable PsiElement targetContainer,
+ @Nullable MoveCallback callback) {
+ if (targetContainer == null || !(targetContainer instanceof PsiDirectory)) {
+ return;
+ }
+ final Course course = CCProjectService.getInstance(project).getCourse();
+ final PsiDirectory sourceDirectory = (PsiDirectory)elements[0];
+ final Lesson sourceLesson = course.getLesson(sourceDirectory.getName());
+ final Lesson targetLesson = course.getLesson(((PsiDirectory)targetContainer).getName());
+ if (targetLesson == null) {
+ Messages.showInfoMessage("Lessons can be moved only to other lessons", "Incorrect Target For Move");
+ return;
+ }
+ final CCMoveStudyItemDialog dialog = new CCMoveStudyItemDialog(project, EduNames.LESSON, targetLesson.getName());
+ dialog.show();
+ if (dialog.getExitCode() != DialogWrapper.OK_EXIT_CODE) {
+ return;
+ }
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ sourceDirectory.getVirtualFile().rename(this, "tmp");
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ }
+ });
+ final VirtualFile[] lessonDirs = project.getBaseDir().getChildren();
+ final Function<VirtualFile, StudyItem> getStudyItem = new Function<VirtualFile, StudyItem>() {
+ @Override
+ public StudyItem fun(VirtualFile file) {
+ return course.getLesson(file.getName());
+ }
+ };
+
+ int sourceLessonIndex = sourceLesson.getIndex();
+ sourceLesson.setIndex(-1);
+ CCUtils.updateHigherElements(lessonDirs, getStudyItem, sourceLessonIndex, EduNames.LESSON, -1);
+
+ final int newItemIndex = targetLesson.getIndex() + dialog.getIndexDelta();
+
+ CCUtils.updateHigherElements(lessonDirs, getStudyItem,
+ newItemIndex - 1, EduNames.LESSON, 1);
+
+ sourceLesson.setIndex(newItemIndex);
+ Collections.sort(course.getLessons(), EduUtils.INDEX_COMPARATOR);
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ sourceDirectory.getVirtualFile().rename(this, EduNames.LESSON + newItemIndex);
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ }
+ });
+ }
+
+ @Override
+ public boolean tryToMove(PsiElement element,
+ Project project,
+ DataContext dataContext,
+ @Nullable PsiReference reference,
+ Editor editor) {
+ return true;
+ }
+}
--- /dev/null
+package com.jetbrains.edu.coursecreator;
+
+import com.intellij.ide.IdeView;
+import com.intellij.ide.util.DirectoryChooserUtil;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.LangDataKeys;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Editor;
+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.PsiElement;
+import com.intellij.psi.PsiReference;
+import com.intellij.refactoring.move.MoveCallback;
+import com.intellij.refactoring.move.MoveHandlerDelegate;
+import com.intellij.util.Function;
+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.StudyItem;
+import com.jetbrains.edu.courseFormat.Task;
+import com.jetbrains.edu.coursecreator.ui.CCMoveStudyItemDialog;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+public class CCTaskMoveHandlerDelegate extends MoveHandlerDelegate {
+
+ private static final Logger LOG = Logger.getInstance(CCTaskMoveHandlerDelegate.class);
+ @Override
+ public boolean canMove(DataContext dataContext) {
+ if (CommonDataKeys.PSI_FILE.getData(dataContext) != null) {
+ return false;
+ }
+ IdeView view = LangDataKeys.IDE_VIEW.getData(dataContext);
+ if (view == null) {
+ return false;
+ }
+ PsiDirectory sourceDirectory = DirectoryChooserUtil.getOrChooseDirectory(view);
+ return isTaskDir(sourceDirectory);
+ }
+
+ @Override
+ public boolean canMove(PsiElement[] elements, @Nullable PsiElement targetContainer) {
+ if (elements.length > 0 && elements[0] instanceof PsiDirectory) {
+ return isTaskDir(((PsiDirectory)elements[0]));
+ }
+ return false;
+ }
+
+ private static boolean isTaskDir(PsiDirectory sourceDirectory) {
+ if (sourceDirectory == null) {
+ return false;
+ }
+ CCProjectService service = CCProjectService.getInstance(sourceDirectory.getProject());
+ Course course = service.getCourse();
+ if (course == null) {
+ return false;
+ }
+ return EduUtils.getTask(sourceDirectory, course) != null;
+ }
+
+ @Override
+ public boolean isValidTarget(PsiElement psiElement, PsiElement[] sources) {
+ return true;
+ }
+
+ @Override
+ public void doMove(final Project project,
+ PsiElement[] elements,
+ @Nullable PsiElement targetContainer,
+ @Nullable MoveCallback callback) {
+ if (targetContainer == null || !(targetContainer instanceof PsiDirectory)) {
+ return;
+ }
+
+ PsiDirectory targetDir = (PsiDirectory)targetContainer;
+ if (!isTaskDir(targetDir) && !CCUtils.isLessonDir(targetDir)) {
+ Messages.showInfoMessage("Tasks can be moved only to other lessons or inside lesson", "Incorrect Target For Move");
+ return;
+ }
+ final Course course = CCProjectService.getInstance(project).getCourse();
+ final PsiDirectory sourceDirectory = (PsiDirectory)elements[0];
+
+ final Task taskToMove = EduUtils.getTask(sourceDirectory, course);
+ if (taskToMove == null) {
+ return;
+ }
+
+ if (CCUtils.isLessonDir(targetDir)) {
+ //if user moves task to any lesson, this task is inserted as the last task in this lesson
+ Lesson targetLesson = course.getLesson(targetDir.getName());
+ if (targetLesson == null) {
+ return;
+ }
+ List<Task> taskList = targetLesson.getTaskList();
+ moveTask(sourceDirectory, taskToMove, taskList.isEmpty() ? null : taskList.get(taskList.size() - 1),
+ 1, targetDir.getVirtualFile(), targetLesson);
+ }
+ else {
+ PsiDirectory lessonDir = targetDir.getParent();
+ if (lessonDir == null) {
+ return;
+ }
+ Task targetTask = EduUtils.getTask(targetDir, course);
+ if (targetTask == null) {
+ return;
+ }
+ final CCMoveStudyItemDialog dialog = new CCMoveStudyItemDialog(project, EduNames.TASK, targetTask.getName());
+ dialog.show();
+ if (dialog.getExitCode() != DialogWrapper.OK_EXIT_CODE) {
+ return;
+ }
+ moveTask(sourceDirectory, taskToMove, targetTask, dialog.getIndexDelta(),
+ lessonDir.getVirtualFile(), targetTask.getLesson());
+ }
+
+ }
+
+ private void moveTask(final PsiDirectory sourceDirectory,
+ final Task taskToMove,
+ Task targetTask,
+ int indexDelta,
+ final VirtualFile targetDirectory,
+ Lesson targetLesson) {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ sourceDirectory.getVirtualFile().rename(this, "tmp");
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ }
+ });
+ final VirtualFile sourceLessonDir = sourceDirectory.getVirtualFile().getParent();
+ if (sourceLessonDir == null) {
+ return;
+ }
+ CCUtils.updateHigherElements(sourceLessonDir.getChildren(), new Function<VirtualFile, StudyItem>() {
+ @Override
+ public StudyItem fun(VirtualFile file) {
+ return taskToMove.getLesson().getTask(file.getName());
+ }
+ }, taskToMove.getIndex(), EduNames.TASK, -1);
+
+ final int newItemIndex = targetTask != null ? targetTask.getIndex() + indexDelta : 1;
+ taskToMove.setIndex(-1);
+ taskToMove.getLesson().getTaskList().remove(taskToMove);
+ final Lesson finalTargetLesson = targetLesson;
+ CCUtils.updateHigherElements(targetDirectory.getChildren(), new Function<VirtualFile, StudyItem>() {
+ @Override
+ public StudyItem fun(VirtualFile file) {
+ return finalTargetLesson.getTask(file.getName());
+ }
+ },
+ newItemIndex - 1, EduNames.TASK, 1);
+
+ taskToMove.setIndex(newItemIndex);
+ taskToMove.setLesson(targetLesson);
+ targetLesson.getTaskList().add(taskToMove);
+ Collections.sort(targetLesson.getTaskList(), EduUtils.INDEX_COMPARATOR);
+
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ //moving file to the same directory leads to exception
+ if (!targetDirectory.equals(sourceLessonDir)) {
+ sourceDirectory.getVirtualFile().move(this, targetDirectory);
+ }
+ sourceDirectory.getVirtualFile().rename(this, EduNames.TASK + newItemIndex);
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ }
+ });
+ }
+
+ @Override
+ public boolean tryToMove(PsiElement element,
+ Project project,
+ DataContext dataContext,
+ @Nullable PsiReference reference,
+ Editor editor) {
+ return true;
+ }
+}
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.util.Function;
});
}
}
+
+ public static boolean isLessonDir(PsiDirectory sourceDirectory) {
+ if (sourceDirectory == null) {
+ return false;
+ }
+ CCProjectService service = CCProjectService.getInstance(sourceDirectory.getProject());
+ Course course = service.getCourse();
+ if (course != null && course.getLesson(sourceDirectory.getName()) != null) {
+ return true;
+ }
+ return false;
+ }
}
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.StudyItem;
@Nullable
@Override
protected StudyItem getThresholdItem(@NotNull Course course, @NotNull PsiDirectory sourceDirectory) {
- PsiDirectory parent = sourceDirectory.getParent();
- if (parent == null) {
- return null;
- }
- Lesson lesson = course.getLesson(parent.getName());
- if (lesson == null) {
- return null;
- }
- return lesson.getTask(sourceDirectory.getName());
+ return EduUtils.getTask(sourceDirectory, course);
}
@Override
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);
+ myPositionalPanel = new CCItemPositionPanel(myItemName, myThresholdName);
}
public String getItemName() {
package com.jetbrains.edu.coursecreator.ui;
+import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBRadioButton;
private JBRadioButton myAfterButton;
private JBLabel mySpecifyPositionLabel;
- public CCItemPositionPanel(String itemName, String thresholdName, int thresholdNum) {
+ public CCItemPositionPanel(String itemName, String thresholdName) {
this.add(myPanel, BorderLayout.CENTER);
- mySpecifyPositionLabel.setText("Specify " + itemName + " position:");
- String postfix = itemName + " " + thresholdNum + " '" + thresholdName + "'";
+ mySpecifyPositionLabel.setText(StringUtil.toTitleCase(itemName) + " position:");
+ String postfix = "'" + thresholdName + "'";
ButtonGroup group = new ButtonGroup();
group.add(myBeforeButton);
group.add(myAfterButton);
--- /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 CCMoveStudyItemDialog extends DialogWrapper {
+ private final CCItemPositionPanel myPanel;
+
+ public CCMoveStudyItemDialog(@Nullable Project project, String itemName, String thresholdName) {
+ super(project);
+ myPanel = new CCItemPositionPanel(itemName, thresholdName);
+ setTitle("Move " + StringUtil.toTitleCase(itemName));
+ init();
+ }
+
+ @Nullable
+ @Override
+ protected JComponent createCenterPanel() {
+ return myPanel;
+ }
+
+ public int getIndexDelta() {
+ return myPanel.getIndexDelta();
+ }
+}
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
-import com.jetbrains.edu.courseFormat.AnswerPlaceholder;
-import com.jetbrains.edu.courseFormat.StudyItem;
-import com.jetbrains.edu.courseFormat.Task;
-import com.jetbrains.edu.courseFormat.TaskFile;
+import com.intellij.psi.PsiDirectory;
+import com.jetbrains.edu.courseFormat.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
});
}
}
+
+ @Nullable
+ public static Task getTask(@NotNull final PsiDirectory directory, @NotNull final Course course) {
+ PsiDirectory lessonDir = directory.getParent();
+ if (lessonDir == null) {
+ return null;
+ }
+ Lesson lesson = course.getLesson(lessonDir.getName());
+ if (lesson == null) {
+ return null;
+ }
+ return lesson.getTask(directory.getName());
+ }
}
if (!EduUtils.indexIsValid(lessonIndex, lessons)) {
return null;
}
- return lessons.get(lessonIndex);
+ for (Lesson lesson : lessons) {
+ if (lesson.getIndex() - 1 == lessonIndex) {
+ return lesson;
+ }
+ }
+ return null;
}
@NotNull
if (!EduUtils.indexIsValid(index, tasks)) {
return null;
}
- return tasks.get(index);
+ for (Task task : tasks) {
+ if (task.getIndex() - 1 == index) {
+ return task;
+ }
+ }
+ return null;
}
}