draw placeholders correctly when switching subtask
authorLiana.Bakradze <liana.bakradze@jetbrains.com>
Thu, 29 Sep 2016 09:37:49 +0000 (12:37 +0300)
committerliana.bakradze <liana.bakradze@jetbrains.com>
Thu, 17 Nov 2016 14:08:17 +0000 (17:08 +0300)
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCAddAnswerPlaceholder.java
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCAnswerPlaceholderAction.java
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCNewSubtaskAction.java
python/educational-core/student/src/com/jetbrains/edu/learning/StudySubtaskUtils.java
python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java
python/educational-core/student/src/com/jetbrains/edu/learning/core/EduAnswerPlaceholderPainter.java
python/educational-core/student/src/com/jetbrains/edu/learning/core/EduDocumentListener.java
python/educational-core/student/src/com/jetbrains/edu/learning/core/EduUtils.java
python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholder.java
python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholderSubtaskInfo.java
python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/TaskFile.java

index 9abfe83f87f7bcadbd2752bab78287efec77ecd2..efb6a91a46b612acc8edd08f5076a722a9c0f91d 100644 (file)
@@ -9,6 +9,7 @@ import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.editor.SelectionModel;
 import com.intellij.openapi.fileEditor.FileDocumentManager;
 import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.TextRange;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.psi.PsiDocumentManager;
 import com.intellij.psi.PsiFile;
@@ -34,7 +35,7 @@ public class CCAddAnswerPlaceholder extends CCAnswerPlaceholderAction {
 
 
   private static boolean arePlaceholdersIntersect(@NotNull final TaskFile taskFile, int start, int end) {
-    List<AnswerPlaceholder> answerPlaceholders = taskFile.getAnswerPlaceholders();
+    List<AnswerPlaceholder> answerPlaceholders = taskFile.getActivePlaceholders();
     for (AnswerPlaceholder existingAnswerPlaceholder : answerPlaceholders) {
       int twStart = existingAnswerPlaceholder.getOffset();
       int twEnd = existingAnswerPlaceholder.getPossibleAnswerLength() + twStart;
@@ -58,10 +59,24 @@ public class CCAddAnswerPlaceholder extends CCAnswerPlaceholderAction {
     FileDocumentManager.getInstance().saveDocument(document);
     final SelectionModel model = editor.getSelectionModel();
     final int offset = model.hasSelection() ? model.getSelectionStart() : editor.getCaretModel().getOffset();
+    TaskFile taskFile = state.getTaskFile();
     int stepIndex = state.getTaskFile().getTask().getActiveSubtaskIndex();
+
+    AnswerPlaceholder existingPlaceholder = taskFile.getAnswerPlaceholder(offset, taskFile.getAnswerPlaceholders());
+    if (existingPlaceholder != null) {
+      int visibleLength = existingPlaceholder.getVisibleLength(stepIndex);
+      int placeholderOffset = existingPlaceholder.getOffset();
+      String possibleAnswer = document.getText(TextRange.create(placeholderOffset, placeholderOffset + visibleLength));
+      AnswerPlaceholderSubtaskInfo info = new AnswerPlaceholderSubtaskInfo();
+      existingPlaceholder.getSubtaskInfos().put(stepIndex, info);
+      info.setPossibleAnswer(possibleAnswer);
+      StudyUtils.drawAllWindows(editor, taskFile);
+      return;
+    }
     final AnswerPlaceholder answerPlaceholder = new AnswerPlaceholder();
-    answerPlaceholder.getSubtaskInfos().put(stepIndex, new AnswerPlaceholderSubtaskInfo());
-    TaskFile taskFile = state.getTaskFile();
+    AnswerPlaceholderSubtaskInfo info = new AnswerPlaceholderSubtaskInfo();
+    info.setNeedInsertText(!model.hasSelection());
+    answerPlaceholder.getSubtaskInfos().put(stepIndex, info);
     int index = taskFile.getAnswerPlaceholders().size();
     answerPlaceholder.setIndex(index);
     answerPlaceholder.setTaskFile(taskFile);
@@ -84,7 +99,6 @@ public class CCAddAnswerPlaceholder extends CCAnswerPlaceholderAction {
       DocumentUtil.writeInRunUndoTransparentAction(() -> document.insertString(offset, defaultPlaceholderText));
     }
 
-
     answerPlaceholder.setPossibleAnswer(model.hasSelection() ? model.getSelectedText() : defaultPlaceholderText);
     AddAction action = new AddAction(answerPlaceholder, taskFile, editor);
     EduUtils.runUndoableAction(project, "Add Answer Placeholder", action);
@@ -187,6 +201,7 @@ public class CCAddAnswerPlaceholder extends CCAnswerPlaceholderAction {
   }
 
   protected CCCreateAnswerPlaceholderDialog createDialog(Project project, AnswerPlaceholder answerPlaceholder) {
-    return new CCCreateAnswerPlaceholderDialog(project, StringUtil.notNullize(answerPlaceholder.getTaskText()), answerPlaceholder.getHints());
+    return new CCCreateAnswerPlaceholderDialog(project, StringUtil.notNullize(answerPlaceholder.getTaskText()),
+                                               answerPlaceholder.getHints());
   }
 }
\ No newline at end of file
index ba3db4d16fb1a5cb74dcceb97b4db6ffc03462ae..9d8bd42cba6388496186ad6ada1175aae1d540e3 100644 (file)
@@ -42,9 +42,7 @@ abstract public class CCAnswerPlaceholderAction extends DumbAwareAction {
     if (taskFile == null) {
       return null;
     }
-    AnswerPlaceholder answerPlaceholder = taskFile.getAnswerPlaceholder(
-      editor.getCaretModel().getOffset()
-    );
+    AnswerPlaceholder answerPlaceholder = taskFile.getAnswerPlaceholder(editor.getCaretModel().getOffset());
     return new CCState(taskFile, answerPlaceholder, psiFile, editor, project);
   }
 
index 57bc3761b06f35c432c9a521f79f507e3e639f26..d3ed834259650420449137f85115cd8d9d9ec02e 100644 (file)
@@ -63,7 +63,6 @@ public class CCNewSubtaskAction extends DumbAwareAction {
     int num = task.getSubtaskNum();
     createTaskDescriptionFile(project, taskDir, num);
     task.setSubtaskNum(num + 1);
-    task.setActiveSubtaskIndex(num);
     StudySubtaskUtils.switchStep(project, task, num);
   }
 
index 81072abf255db65ce18a92b17f23f3c5a336e5f3..ba18c52fd6b0b03a2a35fa5f5e4179117bba3a5c 100644 (file)
@@ -1,17 +1,23 @@
 package com.jetbrains.edu.learning;
 
 import com.intellij.ide.projectView.ProjectView;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.ui.EditorNotifications;
+import com.intellij.util.containers.ContainerUtil;
 import com.jetbrains.edu.learning.checker.StudyCheckUtils;
 import com.jetbrains.edu.learning.core.EduNames;
+import com.jetbrains.edu.learning.core.EduUtils;
+import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
+import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholderSubtaskInfo;
 import com.jetbrains.edu.learning.courseFormat.Task;
 import com.jetbrains.edu.learning.courseFormat.TaskFile;
 import com.jetbrains.edu.learning.ui.StudyToolWindow;
 import org.jetbrains.annotations.NotNull;
 
-import java.util.Map;
+import java.util.*;
 
 public class StudySubtaskUtils {
   private StudySubtaskUtils() {
@@ -21,6 +27,9 @@ public class StudySubtaskUtils {
    * @param toSubtaskIndex from 0 to subtaskNum - 1
    */
   public static void switchStep(@NotNull Project project, @NotNull Task task, int toSubtaskIndex) {
+    if (toSubtaskIndex == task.getActiveSubtaskIndex()) {
+      return;
+    }
     VirtualFile taskDir = task.getTaskDir(project);
     if (taskDir == null) {
       return;
@@ -29,12 +38,15 @@ public class StudySubtaskUtils {
     if (srcDir != null) {
       taskDir = srcDir;
     }
+    int fromSubtaskIndex = task.getActiveSubtaskIndex();
     for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
       String name = entry.getKey();
       VirtualFile virtualFile = taskDir.findChild(name);
       if (virtualFile == null) {
         continue;
       }
+      TaskFile taskFile = entry.getValue();
+      updatePlaceholderTexts(project, virtualFile, taskFile, fromSubtaskIndex, toSubtaskIndex);
       EditorNotifications.getInstance(project).updateNotifications(virtualFile);
     }
     task.setActiveSubtaskIndex(toSubtaskIndex);
@@ -54,4 +66,49 @@ public class StudySubtaskUtils {
       toolWindow.setTaskText(text, taskDir, project);
     }
   }
+
+  private static void updatePlaceholderTexts(@NotNull Project project,
+                                             @NotNull VirtualFile virtualFile,
+                                             @NotNull TaskFile taskFile,
+                                             int fromSubtaskIndex,
+                                             int toSubtaskIndex) {
+    Document document = FileDocumentManager.getInstance().getDocument(virtualFile);
+    if (document == null) {
+      return;
+    }
+    taskFile.setTrackLengths(false);
+    for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) {
+      Set<Integer> indexes = placeholder.getSubtaskInfos().keySet();
+      Integer minIndex = Collections.min(indexes);
+      int visibleLength = placeholder.getVisibleLength(fromSubtaskIndex);
+      if (indexes.contains(toSubtaskIndex) && indexes.contains(fromSubtaskIndex)) {
+        if (!placeholder.getUseLength()) {
+          String replacementText = placeholder.getSubtaskInfos().get(toSubtaskIndex).getPossibleAnswer();
+          EduUtils.replaceAnswerPlaceholder(project, document, placeholder, visibleLength, replacementText);
+        }
+        continue;
+      }
+      if (fromSubtaskIndex < toSubtaskIndex) {
+        if (minIndex > fromSubtaskIndex && minIndex <= toSubtaskIndex) {
+          Integer maxIndex = Collections.max(ContainerUtil.filter(indexes, integer -> integer <= toSubtaskIndex));
+          AnswerPlaceholderSubtaskInfo maxInfo = placeholder.getSubtaskInfos().get(maxIndex);
+          String replacementText = placeholder.getUseLength() ? maxInfo.getPlaceholderText() : maxInfo.getPossibleAnswer();
+          EduUtils.replaceAnswerPlaceholder(project, document, placeholder, visibleLength, replacementText);
+        }
+      }
+      else {
+        if (minIndex > toSubtaskIndex && minIndex <= fromSubtaskIndex) {
+          AnswerPlaceholderSubtaskInfo minInfo = placeholder.getSubtaskInfos().get(minIndex);
+          if (minInfo.isNeedInsertText()) {
+            EduUtils.replaceAnswerPlaceholder(project, document, placeholder, visibleLength, "");
+          }
+          else {
+            String replacementText = minInfo.getPlaceholderText();
+            EduUtils.replaceAnswerPlaceholder(project, document, placeholder, visibleLength, replacementText);
+          }
+        }
+      }
+    }
+    taskFile.setTrackLengths(true);
+  }
 }
\ No newline at end of file
index 450047de2084f9a6f4ff73d2796f5d41bff48de6..b15c1913eb66cb242df4e5fff5437a110cf5108f 100644 (file)
@@ -353,10 +353,11 @@ public class StudyUtils {
     final Project project = editor.getProject();
     if (project == null) return;
     final StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
-    for (AnswerPlaceholder answerPlaceholder : taskFile.getActivePlaceholders()) {
+    for (AnswerPlaceholder answerPlaceholder : taskFile.getAnswerPlaceholders()) {
       final JBColor color = taskManager.getColor(answerPlaceholder);
       EduAnswerPlaceholderPainter.drawAnswerPlaceholder(editor, answerPlaceholder, color);
     }
+
     final Document document = editor.getDocument();
     EditorActionManager.getInstance()
       .setReadonlyFragmentModificationHandler(document, new EduAnswerPlaceholderDeleteHandler(editor));
index b8a841b190816e63dec825acbca4622df1db450b..5628182bd2a16bfff7184717fd54ebfe534c363b 100644 (file)
@@ -33,39 +33,54 @@ public class EduAnswerPlaceholderPainter {
     final TextAttributes textAttributes = new TextAttributes(scheme.getDefaultForeground(), scheme.getDefaultBackground(), null,
                                                                     EffectType.BOXED, Font.PLAIN);
     textAttributes.setEffectColor(color);
-    drawAnswerPlaceholder(editor, placeholder, textAttributes, PLACEHOLDERS_LAYER);
+    if (placeholder.isActive()) {
+      drawAnswerPlaceholder(editor, placeholder, textAttributes, PLACEHOLDERS_LAYER);
+    } else if (!placeholder.getUseLength()) {
+      int offset = placeholder.getOffset();
+      drawAnswerPlaceholderFromPrevStep(editor, offset, offset + placeholder.getVisibleLength(placeholder.getActiveSubtaskIndex()));
+    }
   }
 
   public static void drawAnswerPlaceholder(@NotNull Editor editor,
                                            @NotNull AnswerPlaceholder placeholder,
                                            TextAttributes textAttributes,
                                            int placeholdersLayer) {
-    final Project project = editor.getProject();
-    assert project != null;
     final int startOffset = placeholder.getOffset();
     if (startOffset == - 1) {
       return;
     }
     final int length = placeholder.getRealLength();
     final int endOffset = startOffset + length;
+    drawAnswerPlaceholder(editor, startOffset, endOffset, textAttributes, placeholdersLayer);
+  }
+
+  public static void drawAnswerPlaceholder(@NotNull Editor editor,
+                                           int start,
+                                           int end,
+                                           TextAttributes textAttributes,
+                                           int placeholdersLayer) {
+    final Project project = editor.getProject();
+    assert project != null;
+    if (start == - 1) {
+      return;
+    }
     RangeHighlighter
-      highlighter = editor.getMarkupModel().addRangeHighlighter(startOffset, endOffset, placeholdersLayer,
+      highlighter = editor.getMarkupModel().addRangeHighlighter(start, end, placeholdersLayer,
                                                                 textAttributes, HighlighterTargetArea.EXACT_RANGE);
     highlighter.setGreedyToLeft(true);
     highlighter.setGreedyToRight(true);
   }
 
-  public static void drawAnswerPlaceholderFromPrevStep(@NotNull Editor editor, @NotNull AnswerPlaceholder placeholder) {
-    if (placeholder.getUseLength()) {
-      return;
-    }
+
+
+  public static void drawAnswerPlaceholderFromPrevStep(@NotNull Editor editor, int start, int end) {
     EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme();
     Color color = scheme.getColor(EditorColors.TEARLINE_COLOR);
     SimpleTextAttributes attributes = SimpleTextAttributes.GRAY_ATTRIBUTES;
     final TextAttributes textAttributes = new TextAttributes(attributes.getFgColor(), color, null,
                                                              null, attributes.getFontStyle());
 
-    drawAnswerPlaceholder(editor, placeholder, textAttributes, HighlighterLayer.LAST);
+    drawAnswerPlaceholder(editor, start, end, textAttributes, HighlighterLayer.LAST);
   }
 
   public static void createGuardedBlock(Editor editor, List<RangeMarker> blocks, int start, int end) {
index 11e7aa34995260e46566918a57b7d3c142fca964..eeb126b9dfca0b485be3e7f5f6234ea1354955d6 100644 (file)
@@ -73,7 +73,9 @@ public class EduDocumentListener extends DocumentAdapter {
           if (answerPlaceholder.getUseLength()) {
             answerPlaceholder.setLength(length);
           } else {
-            answerPlaceholder.setPossibleAnswer(document.getText(TextRange.create(twStart, twStart + length)));
+            if (answerPlaceholder.isActive() && myTaskFile.isTrackLengths()) {
+              answerPlaceholder.setPossibleAnswer(document.getText(TextRange.create(twStart, twStart + length)));
+            }
           }
         }
       }
index 3ba5b458f85561b20e48d72e840a21ff42bdbe2e..6a7bb8c1b3e64e06898320e1629af07770f36115 100644 (file)
@@ -164,19 +164,20 @@ public class EduUtils {
     studentDocument.addDocumentListener(listener);
 
     for (AnswerPlaceholder placeholder : taskFile.getActivePlaceholders()) {
-      replaceAnswerPlaceholder(project, studentDocument, placeholder);
+      replaceAnswerPlaceholder(project, studentDocument, placeholder, placeholder.getRealLength(), placeholder.getTaskText());
     }
     studentDocument.removeDocumentListener(listener);
     return Pair.create(studentFile, taskFile);
   }
 
-  private static void replaceAnswerPlaceholder(@NotNull final Project project,
-                                               @NotNull final Document document,
-                                               @NotNull final AnswerPlaceholder answerPlaceholder) {
-    final String taskText = answerPlaceholder.getTaskText();
+  public static void replaceAnswerPlaceholder(@NotNull final Project project,
+                                              @NotNull final Document document,
+                                              @NotNull final AnswerPlaceholder answerPlaceholder,
+                                              int length,
+                                              String replacementText) {
     final int offset = answerPlaceholder.getOffset();
     CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> {
-      document.replaceString(offset, offset + answerPlaceholder.getRealLength(), taskText);
+      document.replaceString(offset, offset + length, replacementText);
       FileDocumentManager.getInstance().saveDocument(document);
     }), "Replace Answer Placeholders", "Replace Answer Placeholders");
   }
index 0dc01b6f1f648f41748584f5d858257fb417f5bd..556e592c648c963a8574078d014942a34a463109 100644 (file)
@@ -2,10 +2,12 @@ package com.jetbrains.edu.learning.courseFormat;
 
 import com.google.gson.annotations.Expose;
 import com.google.gson.annotations.SerializedName;
-import com.intellij.util.containers.HashMap;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.containers.hash.HashMap;
 import com.intellij.util.xmlb.annotations.Transient;
 import org.jetbrains.annotations.NotNull;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -138,7 +140,7 @@ public class AnswerPlaceholder {
    * @return length or possible answer length
    */
   public int getRealLength() {
-    return myUseLength ? getLength() : getPossibleAnswerLength();
+    return myUseLength ? getLength() : getVisibleLength(getActiveSubtaskIndex());
   }
 
   public void setUseLength(boolean useLength) {
@@ -179,6 +181,10 @@ public class AnswerPlaceholder {
     mySubtaskInfos = subtaskInfos;
   }
 
+  public boolean isActive() {
+    return getActiveSubtaskInfo() != null;
+  }
+
   public static class MyInitialState {
     private int length = -1;
     private int offset = -1;
@@ -209,10 +215,26 @@ public class AnswerPlaceholder {
   }
 
   public AnswerPlaceholderSubtaskInfo getActiveSubtaskInfo() {
+    return mySubtaskInfos.get(getActiveSubtaskIndex());
+  }
+
+  public int getActiveSubtaskIndex() {
     if (myTaskFile == null || myTaskFile.getTask() == null) {
-      return mySubtaskInfos.get(0);
+      return 0;
+    }
+    return myTaskFile.getTask().getActiveSubtaskIndex();
+  }
+
+  public int getVisibleLength(int subtaskIndex) {
+    int minIndex = Collections.min(mySubtaskInfos.keySet());
+    AnswerPlaceholderSubtaskInfo minInfo = mySubtaskInfos.get(minIndex);
+    if (minIndex == subtaskIndex) {
+      return getUseLength() ? length : minInfo.getPossibleAnswer().length();
+    }
+    if (minIndex > subtaskIndex) {
+      return minInfo.isNeedInsertText() ? 0 : minInfo.getPlaceholderText().length();
     }
-    int activeStepIndex = myTaskFile.getTask().getActiveSubtaskIndex();
-    return mySubtaskInfos.get(activeStepIndex);
+    int maxIndex = Collections.max(ContainerUtil.filter(mySubtaskInfos.keySet(), i -> i < subtaskIndex));
+    return getUseLength() ? length : mySubtaskInfos.get(maxIndex).getPossibleAnswer().length();
   }
 }
index b648592b087b2cbd7e9302c2f0b117f2d4adfb46..f7f3d2b859646854878b3bf2d9b197bddbe05871 100644 (file)
@@ -26,6 +26,7 @@ public class AnswerPlaceholderSubtaskInfo {
   private boolean mySelected = false;
   private StudyStatus myStatus = StudyStatus.Unchecked;
   @Expose private boolean myHasFrame = true;
+  @Expose private boolean myNeedInsertText = false;
 
   public StudyStatus getStatus() {
     return myStatus;
@@ -127,4 +128,12 @@ public class AnswerPlaceholderSubtaskInfo {
   public void setHasFrame(boolean hasFrame) {
     myHasFrame = hasFrame;
   }
+
+  public boolean isNeedInsertText() {
+    return myNeedInsertText;
+  }
+
+  public void setNeedInsertText(boolean needInsertText) {
+    myNeedInsertText = needInsertText;
+  }
 }
index 9682cebd5a70a164353fd1cc2b50cf9097740606..4358ee62d62cf50d67580314679d1cadf85c07c6 100644 (file)
@@ -21,6 +21,7 @@ public class TaskFile {
   private int myIndex = -1;
   private boolean myUserCreated = false;
   private boolean myTrackChanges = true;
+  private boolean myTrackLengths = true;
   private boolean myHighlightErrors = false;
   @Expose @SerializedName("placeholders") private List<AnswerPlaceholder> myAnswerPlaceholders = new ArrayList<>();
   @Transient private Task myTask;
@@ -86,7 +87,11 @@ public class TaskFile {
    */
   @Nullable
   public AnswerPlaceholder getAnswerPlaceholder(int offset) {
-    for (AnswerPlaceholder placeholder : myAnswerPlaceholders) {
+    return getAnswerPlaceholder(offset, getActivePlaceholders());
+  }
+
+  @Nullable public AnswerPlaceholder getAnswerPlaceholder(int offset, List<AnswerPlaceholder> placeholders) {
+    for (AnswerPlaceholder placeholder : placeholders) {
       int placeholderStart = placeholder.getOffset();
       int placeholderEnd = placeholderStart + placeholder.getRealLength();
       if (placeholderStart <= offset && offset <= placeholderEnd) {
@@ -96,6 +101,13 @@ public class TaskFile {
     return null;
   }
 
+  public boolean isTrackLengths() {
+    return myTrackLengths;
+  }
+
+  public void setTrackLengths(boolean trackLengths) {
+    myTrackLengths = trackLengths;
+  }
 
   public static void copy(@NotNull final TaskFile source, @NotNull final TaskFile target) {
     List<AnswerPlaceholder> sourceAnswerPlaceholders = source.getActivePlaceholders();