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;
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;
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);
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);
}
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
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);
}
int num = task.getSubtaskNum();
createTaskDescriptionFile(project, taskDir, num);
task.setSubtaskNum(num + 1);
- task.setActiveSubtaskIndex(num);
StudySubtaskUtils.switchStep(project, task, num);
}
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() {
* @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;
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);
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
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));
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) {
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)));
+ }
}
}
}
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");
}
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;
* @return length or possible answer length
*/
public int getRealLength() {
- return myUseLength ? getLength() : getPossibleAnswerLength();
+ return myUseLength ? getLength() : getVisibleLength(getActiveSubtaskIndex());
}
public void setUseLength(boolean useLength) {
mySubtaskInfos = subtaskInfos;
}
+ public boolean isActive() {
+ return getActiveSubtaskInfo() != null;
+ }
+
public static class MyInitialState {
private int length = -1;
private int offset = -1;
}
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();
}
}
private boolean mySelected = false;
private StudyStatus myStatus = StudyStatus.Unchecked;
@Expose private boolean myHasFrame = true;
+ @Expose private boolean myNeedInsertText = false;
public StudyStatus getStatus() {
return myStatus;
public void setHasFrame(boolean hasFrame) {
myHasFrame = hasFrame;
}
+
+ public boolean isNeedInsertText() {
+ return myNeedInsertText;
+ }
+
+ public void setNeedInsertText(boolean needInsertText) {
+ myNeedInsertText = needInsertText;
+ }
}
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;
*/
@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) {
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();