1 package com.jetbrains.edu.coursecreator;
3 import com.intellij.openapi.application.ApplicationManager;
4 import com.intellij.openapi.diagnostic.Logger;
5 import com.intellij.openapi.editor.colors.EditorColors;
6 import com.intellij.openapi.editor.colors.EditorColorsManager;
7 import com.intellij.openapi.fileEditor.FileEditor;
8 import com.intellij.openapi.fileEditor.FileEditorManager;
9 import com.intellij.openapi.project.DumbAware;
10 import com.intellij.openapi.project.Project;
11 import com.intellij.openapi.ui.popup.JBPopupFactory;
12 import com.intellij.openapi.ui.popup.ListSeparator;
13 import com.intellij.openapi.ui.popup.PopupStep;
14 import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
15 import com.intellij.openapi.util.Key;
16 import com.intellij.openapi.util.io.FileUtil;
17 import com.intellij.openapi.util.io.FileUtilRt;
18 import com.intellij.openapi.vfs.VirtualFile;
19 import com.intellij.ui.EditorNotificationPanel;
20 import com.intellij.ui.EditorNotifications;
21 import com.intellij.ui.awt.RelativePoint;
22 import com.intellij.util.containers.ContainerUtil;
23 import com.jetbrains.edu.coursecreator.actions.CCNewSubtaskAction;
24 import com.jetbrains.edu.learning.StudySubtaskUtils;
25 import com.jetbrains.edu.learning.StudyUtils;
26 import com.jetbrains.edu.learning.core.EduNames;
27 import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
28 import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholderSubtaskInfo;
29 import com.jetbrains.edu.learning.courseFormat.Task;
30 import com.jetbrains.edu.learning.courseFormat.TaskFile;
31 import org.jetbrains.annotations.NotNull;
32 import org.jetbrains.annotations.Nullable;
35 import java.io.IOException;
37 import java.util.List;
39 public class CCSubtaskEditorNotificationProvider extends EditorNotifications.Provider<EditorNotificationPanel> implements DumbAware {
40 private static final Key<EditorNotificationPanel> KEY = Key.create("edu.coursecreator.subtask");
41 public static final String SWITCH_SUBTASK = "Switch subtask";
42 public static final Integer ADD_SUBTASK_ID = -1;
43 private static final Logger LOG = Logger.getInstance(CCSubtaskEditorNotificationProvider.class);
44 private final Project myProject;
46 public CCSubtaskEditorNotificationProvider(Project project) {
52 public Key<EditorNotificationPanel> getKey() {
58 public EditorNotificationPanel createNotificationPanel(@NotNull VirtualFile file, @NotNull FileEditor fileEditor) {
59 if (!CCUtils.isCourseCreator(myProject)) {
62 boolean isTestFile = CCUtils.isTestsFile(myProject, file);
63 if (!isTestFile && StudyUtils.getTaskFile(myProject, file) == null) {
66 Task task = StudyUtils.getTaskForFile(myProject, file);
67 if (task == null || !task.hasSubtasks()) {
70 EditorNotificationPanel panel = new EditorNotificationPanel() {
72 public Color getBackground() {
73 Color color = EditorColorsManager.getInstance().getGlobalScheme().getColor(EditorColors.GUTTER_BACKGROUND);
74 return color == null ? super.getBackground() : color;
77 String header = isTestFile ? "test" : "task file";
78 int activeSubtaskIndex = task.getActiveSubtaskIndex() + 1;
79 int subtaskSize = task.getLastSubtaskIndex() + 1;
80 panel.setText("This is " + header + " for " + EduNames.SUBTASK + " " + activeSubtaskIndex + "/" + subtaskSize);
81 panel.createActionLabel(SWITCH_SUBTASK, () -> {
82 ArrayList<Integer> values = new ArrayList<>();
83 for (int i = 0; i <= task.getLastSubtaskIndex(); i++) {
86 values.add(ADD_SUBTASK_ID);
87 JBPopupFactory.getInstance().createListPopup(new SwitchSubtaskPopupStep(SWITCH_SUBTASK, values, task, file))
88 .show(RelativePoint.getSouthEastOf(panel));
93 private class SwitchSubtaskPopupStep extends BaseListPopupStep<Integer> {
94 private final Task myTask;
95 private final VirtualFile myFile;
97 public SwitchSubtaskPopupStep(@Nullable String title,
99 Task task, VirtualFile file) {
100 super(title, values);
107 public String getTextFor(Integer value) {
108 if (value.equals(ADD_SUBTASK_ID)) {
109 return CCNewSubtaskAction.NEW_SUBTASK;
111 int subtaskNum = value + 1;
112 String text = EduNames.SUBTASK + " " + subtaskNum;
113 if (value == myTask.getActiveSubtaskIndex()) {
114 text += " (selected)";
120 public PopupStep onChosen(Integer selectedValue, boolean finalChoice) {
122 if (selectedValue.equals(ADD_SUBTASK_ID)) {
123 return doFinalStep(() -> CCNewSubtaskAction.addSubtask(myFile, myProject));
125 StudySubtaskUtils.switchStep(myProject, myTask, selectedValue);
128 if (hasSubstep(selectedValue)) {
129 return new ActionsPopupStep(myTask, selectedValue);
132 return super.onChosen(selectedValue, false);
136 public boolean hasSubstep(Integer selectedValue) {
137 return !selectedValue.equals(ADD_SUBTASK_ID);
141 public int getDefaultOptionIndex() {
142 return myTask.getActiveSubtaskIndex();
147 public ListSeparator getSeparatorAbove(Integer value) {
148 return value.equals(ADD_SUBTASK_ID) ? new ListSeparator() : null;
152 private class ActionsPopupStep extends BaseListPopupStep<String> {
154 public static final String SELECT = "Select";
155 public static final String DELETE = "Delete";
156 private final Task myTask;
157 private final int mySubtaskIndex;
159 public ActionsPopupStep(Task task, int subtaskIndex) {
160 super(null, Arrays.asList(SELECT, DELETE));
162 mySubtaskIndex = subtaskIndex;
166 public PopupStep onChosen(String selectedValue, boolean finalChoice) {
168 if (selectedValue.equals(SELECT)) {
169 StudySubtaskUtils.switchStep(myProject, myTask, mySubtaskIndex);
172 for (TaskFile taskFile : myTask.getTaskFiles().values()) {
173 List<AnswerPlaceholder> emptyPlaceholders = new ArrayList<>();
174 for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) {
175 Map<Integer, AnswerPlaceholderSubtaskInfo> infos = placeholder.getSubtaskInfos();
176 if (infos.containsKey(mySubtaskIndex)) {
177 infos.remove(mySubtaskIndex);
178 if (infos.isEmpty()) {
179 emptyPlaceholders.add(placeholder);
183 taskFile.getAnswerPlaceholders().removeAll(emptyPlaceholders);
185 VirtualFile taskDir = myTask.getTaskDir(myProject);
186 if (taskDir == null) {
189 deleteSubtaskFiles(taskDir);
190 if (mySubtaskIndex != myTask.getLastSubtaskIndex()) {
191 renameFiles(taskDir);
194 myTask.setLastSubtaskIndex(myTask.getLastSubtaskIndex() - 1);
195 int activeSubtaskIndex = myTask.getActiveSubtaskIndex();
196 if (mySubtaskIndex != 0 && activeSubtaskIndex == mySubtaskIndex) {
197 StudySubtaskUtils.switchStep(myProject, myTask, mySubtaskIndex - 1);
199 if (activeSubtaskIndex > mySubtaskIndex) {
200 myTask.setActiveSubtaskIndex(activeSubtaskIndex - 1);
202 StudySubtaskUtils.updateUI(myProject, myTask, taskDir);
203 for (VirtualFile file : FileEditorManager.getInstance(myProject).getOpenFiles()) {
204 EditorNotifications.getInstance(myProject).updateNotifications(file);
210 return super.onChosen(selectedValue, finalChoice);
213 private void updateInfoIndexes() {
214 for (TaskFile taskFile : myTask.getTaskFiles().values()) {
215 for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) {
216 List<Integer> filtered = ContainerUtil.filter(placeholder.getSubtaskInfos().keySet(), index -> index > mySubtaskIndex);
217 Map<Integer, AnswerPlaceholderSubtaskInfo> savedInfos = new HashMap<>();
218 for (Integer index : filtered) {
219 savedInfos.put(index, placeholder.getSubtaskInfos().get(index));
220 placeholder.getSubtaskInfos().remove(index);
222 for (Integer index : filtered) {
223 placeholder.getSubtaskInfos().put(index - 1, savedInfos.get(index));
229 private void renameFiles(VirtualFile taskDir) {
230 ApplicationManager.getApplication().runWriteAction(() -> {
231 Map<VirtualFile, String> newNames = new HashMap<>();
232 for (VirtualFile virtualFile : taskDir.getChildren()) {
233 int subtaskIndex = CCUtils.getSubtaskIndex(myProject, virtualFile);
234 if (subtaskIndex == -1) {
237 if (subtaskIndex > mySubtaskIndex) {
238 String index = subtaskIndex == 1 ? "" : Integer.toString(subtaskIndex - 1);
239 String fileName = virtualFile.getName();
240 String nameWithoutExtension = FileUtil.getNameWithoutExtension(fileName);
241 String extension = FileUtilRt.getExtension(fileName);
242 int subtaskMarkerIndex = nameWithoutExtension.indexOf(EduNames.SUBTASK_MARKER);
243 String newName = subtaskMarkerIndex == -1
244 ? nameWithoutExtension
245 : nameWithoutExtension.substring(0, subtaskMarkerIndex);
246 newName += index.isEmpty() ? "" : EduNames.SUBTASK_MARKER;
247 newName += index + "." + extension;
248 newNames.put(virtualFile, newName);
251 for (Map.Entry<VirtualFile, String> entry : newNames.entrySet()) {
253 entry.getKey().rename(myProject, entry.getValue());
255 catch (IOException e) {
262 private void deleteSubtaskFiles(VirtualFile taskDir) {
263 ApplicationManager.getApplication().runWriteAction(() -> {
264 List<VirtualFile> filesToDelete = new ArrayList<>();
265 for (VirtualFile file : taskDir.getChildren()) {
266 int index = CCUtils.getSubtaskIndex(myProject, file);
267 if (index != -1 && mySubtaskIndex == index) {
268 filesToDelete.add(file);
271 for (VirtualFile file : filesToDelete) {
273 file.delete(myProject);
275 catch (IOException e) {