EDU-658 Create placeholder without selection
[idea/community.git] / python / educational-core / course-creator / src / com / jetbrains / edu / coursecreator / actions / CCAddAnswerPlaceholder.java
1 package com.jetbrains.edu.coursecreator.actions;
2
3 import com.intellij.openapi.actionSystem.AnActionEvent;
4 import com.intellij.openapi.actionSystem.Presentation;
5 import com.intellij.openapi.editor.Document;
6 import com.intellij.openapi.editor.Editor;
7 import com.intellij.openapi.editor.SelectionModel;
8 import com.intellij.openapi.fileEditor.FileDocumentManager;
9 import com.intellij.openapi.project.Project;
10 import com.intellij.openapi.ui.DialogWrapper;
11 import com.intellij.psi.PsiDocumentManager;
12 import com.intellij.psi.PsiFile;
13 import com.intellij.ui.JBColor;
14 import com.intellij.util.DocumentUtil;
15 import com.jetbrains.edu.coursecreator.ui.CCCreateAnswerPlaceholderDialog;
16 import com.jetbrains.edu.learning.StudyUtils;
17 import com.jetbrains.edu.learning.core.EduAnswerPlaceholderPainter;
18 import com.jetbrains.edu.learning.core.EduNames;
19 import com.jetbrains.edu.learning.core.EduUtils;
20 import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
21 import com.jetbrains.edu.learning.courseFormat.TaskFile;
22 import org.jetbrains.annotations.NotNull;
23
24 import java.util.List;
25
26 public class CCAddAnswerPlaceholder extends CCAnswerPlaceholderAction {
27
28   public CCAddAnswerPlaceholder() {
29     super("Add/Delete Answer Placeholder", "Add/Delete answer placeholder");
30   }
31
32
33   private static boolean arePlaceholdersIntersect(@NotNull final TaskFile taskFile, int start, int end) {
34     List<AnswerPlaceholder> answerPlaceholders = taskFile.getAnswerPlaceholders();
35     for (AnswerPlaceholder existingAnswerPlaceholder : answerPlaceholders) {
36       int twStart = existingAnswerPlaceholder.getOffset();
37       int twEnd = existingAnswerPlaceholder.getPossibleAnswerLength() + twStart;
38       if ((start >= twStart && start < twEnd) || (end > twStart && end <= twEnd) ||
39           (twStart >= start && twStart < end) || (twEnd > start && twEnd <= end)) {
40         return true;
41       }
42     }
43     return false;
44   }
45
46   private static void addPlaceholder(@NotNull CCState state) {
47     Editor editor = state.getEditor();
48     Project project = state.getProject();
49     PsiFile file = state.getFile();
50
51     final Document document = PsiDocumentManager.getInstance(project).getDocument(file);
52     if (document == null) {
53       return;
54     }
55
56     final SelectionModel model = editor.getSelectionModel();
57     final int offset = model.hasSelection() ? model.getSelectionStart() : editor.getCaretModel().getOffset();
58     final AnswerPlaceholder answerPlaceholder = new AnswerPlaceholder();
59
60     answerPlaceholder.setOffset(offset);
61     answerPlaceholder.setUseLength(false);
62
63     answerPlaceholder.setPossibleAnswer(model.hasSelection() ? model.getSelectedText() : EduNames.PLACEHOLDER);
64
65     CCCreateAnswerPlaceholderDialog dlg = new CCCreateAnswerPlaceholderDialog(project, answerPlaceholder);
66     dlg.show();
67     if (dlg.getExitCode() != DialogWrapper.OK_EXIT_CODE) {
68       return;
69     }
70
71     if (!model.hasSelection()) {
72       DocumentUtil.writeInRunUndoTransparentAction(() -> document.insertString(offset, EduNames.PLACEHOLDER));
73     }
74
75     TaskFile taskFile = state.getTaskFile();
76     int index = taskFile.getAnswerPlaceholders().size() + 1;
77     answerPlaceholder.setIndex(index);
78     taskFile.addAnswerPlaceholder(answerPlaceholder);
79     answerPlaceholder.setTaskFile(taskFile);
80     taskFile.sortAnswerPlaceholders();
81
82
83     computeInitialState(project, file, taskFile, document);
84
85     EduAnswerPlaceholderPainter.drawAnswerPlaceholder(editor, answerPlaceholder, JBColor.BLUE);
86     EduAnswerPlaceholderPainter.createGuardedBlocks(editor, answerPlaceholder);
87   }
88
89   private static void computeInitialState(Project project, PsiFile file, TaskFile taskFile, Document document) {
90     Document patternDocument = StudyUtils.getPatternDocument(taskFile, file.getName());
91     if (patternDocument == null) {
92       return;
93     }
94     DocumentUtil.writeInRunUndoTransparentAction(() -> {
95       patternDocument.replaceString(0, patternDocument.getTextLength(), document.getCharsSequence());
96       FileDocumentManager.getInstance().saveDocument(patternDocument);
97     });
98     TaskFile target = new TaskFile();
99     TaskFile.copy(taskFile, target);
100     List<AnswerPlaceholder> placeholders = target.getAnswerPlaceholders();
101     for (AnswerPlaceholder placeholder : placeholders) {
102       placeholder.setUseLength(false);
103     }
104     EduUtils.createStudentDocument(project, target, file.getVirtualFile(), patternDocument);
105
106     for (int i = 0; i < placeholders.size(); i++) {
107       AnswerPlaceholder fromPlaceholder = placeholders.get(i);
108       taskFile.getAnswerPlaceholders().get(i).setInitialState(new AnswerPlaceholder.MyInitialState(fromPlaceholder.getOffset(), fromPlaceholder.getLength()));
109     }
110   }
111
112   @Override
113   protected void performAnswerPlaceholderAction(@NotNull CCState state) {
114     if (canAddPlaceholder(state)) {
115       addPlaceholder(state);
116       return;
117     }
118     if (canDeletePlaceholder(state)) {
119       deletePlaceholder(state);
120     }
121   }
122
123   private static void deletePlaceholder(@NotNull CCState state) {
124     Project project = state.getProject();
125     PsiFile psiFile = state.getFile();
126     final Document document = PsiDocumentManager.getInstance(project).getDocument(psiFile);
127     if (document == null) return;
128     TaskFile taskFile = state.getTaskFile();
129     AnswerPlaceholder answerPlaceholder = state.getAnswerPlaceholder();
130     final List<AnswerPlaceholder> answerPlaceholders = taskFile.getAnswerPlaceholders();
131     if (answerPlaceholders.contains(answerPlaceholder)) {
132       answerPlaceholders.remove(answerPlaceholder);
133       final Editor editor = state.getEditor();
134       editor.getMarkupModel().removeAllHighlighters();
135       StudyUtils.drawAllWindows(editor, taskFile);
136       EduAnswerPlaceholderPainter.createGuardedBlocks(editor, taskFile);
137     }
138   }
139
140   @Override
141   public void update(@NotNull AnActionEvent event) {
142     final Presentation presentation = event.getPresentation();
143     presentation.setEnabledAndVisible(false);
144
145     CCState state = getState(event);
146     if (state == null) {
147       return;
148     }
149
150     presentation.setVisible(true);
151     if (canAddPlaceholder(state) || canDeletePlaceholder(state)) {
152       presentation.setEnabled(true);
153       presentation.setText((state.getAnswerPlaceholder() == null ? "Add " : "Delete ") + EduNames.ANSWER_PLACEHOLDER);
154     }
155   }
156
157
158   private static boolean canAddPlaceholder(@NotNull CCState state) {
159     Editor editor = state.getEditor();
160     SelectionModel selectionModel = editor.getSelectionModel();
161     if (selectionModel.hasSelection()) {
162       int start = selectionModel.getSelectionStart();
163       int end = selectionModel.getSelectionEnd();
164       return !arePlaceholdersIntersect(state.getTaskFile(), start, end);
165     }
166     int offset = editor.getCaretModel().getOffset();
167     return state.getTaskFile().getAnswerPlaceholder(offset) == null;
168   }
169
170   private static boolean canDeletePlaceholder(@NotNull CCState state) {
171     if (state.getEditor().getSelectionModel().hasSelection()) {
172       return false;
173     }
174     return state.getAnswerPlaceholder() != null;
175   }
176 }