add offset listeners

This commit is contained in:
Liana Bakradze
2014-08-28 14:24:18 +04:00
parent 4de3f75ca6
commit bbe9991f6a
7 changed files with 325 additions and 25 deletions
@@ -1,5 +1,6 @@
package org.jetbrains.plugins.coursecreator;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.event.EditorFactoryEvent;
import com.intellij.openapi.editor.event.EditorFactoryListener;
@@ -7,7 +8,7 @@ import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.coursecreator.format.Course;
import org.jetbrains.plugins.coursecreator.format.*;
public class CCEditorFactoryListener implements EditorFactoryListener {
@Override
@@ -25,11 +26,55 @@ public class CCEditorFactoryListener implements EditorFactoryListener {
if (course == null) {
return;
}
final VirtualFile taskDir = virtualFile.getParent();
if (taskDir == null || !taskDir.getName().contains("task")) {
return;
}
final VirtualFile lessonDir = taskDir.getParent();
if (lessonDir == null) return;
final Lesson lesson = course.getLesson(lessonDir.getName());
final Task task = lesson.getTask(taskDir.getName());
final TaskFile taskFile = task.getTaskFile(virtualFile.getName());
TaskFileModificationListener listener = new TaskFileModificationListener(taskFile);
CCProjectService.addDocumentListener(editor.getDocument(), listener);
editor.getDocument().addDocumentListener(listener);
CCProjectService.drawTaskWindows(virtualFile, editor, course);
}
@Override
public void editorReleased(@NotNull EditorFactoryEvent event) {
Editor editor = event.getEditor();
Document document = editor.getDocument();
StudyDocumentListener listener = CCProjectService.getListener(document);
if (listener != null) {
document.removeDocumentListener(listener);
CCProjectService.removeListener(document);
}
editor.getMarkupModel().removeAllHighlighters();
editor.getSelectionModel().removeSelection();
}
private class TaskFileModificationListener extends StudyDocumentListener {
private final TaskFile myTaskFile;
public TaskFileModificationListener(TaskFile taskFile) {
super(taskFile);
myTaskFile = taskFile;
}
@Override
protected void updateTaskWindowLength(CharSequence fragment, TaskWindow taskWindow, int change) {
int newLength = taskWindow.getReplacementLength() + change;
taskWindow.setReplacementLength(newLength <= 0 ? 0 : newLength);
if (fragment.equals("\n")) {
taskWindow.setReplacementLength(taskWindow.getLength() + 1);
}
}
@Override
protected boolean needModify() {
return myTaskFile.isTrackChanges();
}
}
}
@@ -3,10 +3,13 @@ package org.jetbrains.plugins.coursecreator;
import com.intellij.openapi.components.ProjectComponent;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.event.EditorFactoryEvent;
import com.intellij.openapi.editor.impl.EditorFactoryImpl;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.impl.text.PsiAwareTextEditorImpl;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
@@ -42,7 +45,7 @@ public class CCProjectComponent implements ProjectComponent {
FileEditor fileEditor = FileEditorManager.getInstance(myProject).getSelectedEditor(file);
if (fileEditor instanceof PsiAwareTextEditorImpl) {
Editor editor = ((PsiAwareTextEditorImpl)fileEditor).getEditor();
CCProjectService.drawTaskWindows(file, editor, course);
new CCEditorFactoryListener().editorCreated(new EditorFactoryEvent(new EditorFactoryImpl(ProjectManager.getInstance()), editor ));
}
}
}
@@ -21,6 +21,7 @@ import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
@@ -31,7 +32,9 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.coursecreator.format.*;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@State(name = "CCProjectService",
storages = {
@@ -43,6 +46,7 @@ public class CCProjectService implements PersistentStateComponent<Element> {
private static final Logger LOG = Logger.getInstance(CCProjectService.class.getName());
public Course myCourse;
public static final String COURSE_ELEMENT = "course";
private static final Map<Document, StudyDocumentListener> myDocumentListeners = new HashMap<Document, StudyDocumentListener>();
public void setCourse(@NotNull final Course course) {
myCourse = course;
@@ -114,4 +118,21 @@ public class CCProjectService implements PersistentStateComponent<Element> {
taskWindow.drawHighlighter(editor);
}
}
public static void addDocumentListener(Document document, StudyDocumentListener listener) {
myDocumentListeners.put(document, listener);
}
public static StudyDocumentListener getListener(Document document) {
return myDocumentListeners.get(document);
}
public static void removeListener(Document document) {
myDocumentListeners.remove(document);
}
public static boolean indexIsValid(int index, List<TaskWindow> collection) {
int size = collection.size();
return index >= 0 && index < size;
}
}
@@ -0,0 +1,71 @@
package org.jetbrains.plugins.coursecreator;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.editor.event.DocumentAdapter;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.impl.event.DocumentEventImpl;
import org.jetbrains.plugins.coursecreator.format.TaskFile;
import org.jetbrains.plugins.coursecreator.format.TaskWindow;
/**
* author: liana
* data: 7/16/14.
* Listens changes in study files and updates
* coordinates of all the windows in current task file
*/
public abstract class StudyDocumentListener extends DocumentAdapter {
private final TaskFile myTaskFile;
private int oldLine;
private int oldLineStartOffset;
private TaskWindow myTaskWindow;
public StudyDocumentListener(TaskFile taskFile) {
myTaskFile = taskFile;
}
//remembering old end before document change because of problems
// with fragments containing "\n"
@Override
public void beforeDocumentChange(DocumentEvent e) {
int offset = e.getOffset();
int oldEnd = offset + e.getOldLength();
Document document = e.getDocument();
oldLine = document.getLineNumber(oldEnd);
oldLineStartOffset = document.getLineStartOffset(oldLine);
int line = document.getLineNumber(offset);
int offsetInLine = offset - document.getLineStartOffset(line);
LogicalPosition pos = new LogicalPosition(line, offsetInLine);
myTaskWindow = myTaskFile.getTaskWindow(document, pos);
}
@Override
public void documentChanged(DocumentEvent e) {
if (e instanceof DocumentEventImpl) {
if (!needModify()) {
return;
}
DocumentEventImpl event = (DocumentEventImpl)e;
Document document = e.getDocument();
int offset = e.getOffset();
int change = event.getNewLength() - event.getOldLength();
if (myTaskWindow != null) {
updateTaskWindowLength(e.getNewFragment(), myTaskWindow, change);
}
int newEnd = offset + event.getNewLength();
int newLine = document.getLineNumber(newEnd);
int lineChange = newLine - oldLine;
myTaskFile.incrementLines(oldLine + 1, lineChange);
int newEndOffsetInLine = offset + e.getNewLength() - document.getLineStartOffset(newLine);
int oldEndOffsetInLine = offset + e.getOldLength() - oldLineStartOffset;
myTaskFile.updateLine(lineChange, oldLine, newEndOffsetInLine, oldEndOffsetInLine);
}
}
protected abstract void updateTaskWindowLength(CharSequence fragment, TaskWindow taskWindow, int change);
protected abstract boolean needModify();
}
@@ -8,12 +8,9 @@ import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.command.undo.UndoManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
@@ -22,13 +19,12 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.util.io.ZipUtil;
import org.jetbrains.plugins.coursecreator.CCProjectService;
import org.jetbrains.plugins.coursecreator.StudyDocumentListener;
import org.jetbrains.plugins.coursecreator.format.*;
import org.jetbrains.plugins.coursecreator.ui.CreateCourseArchiveDialog;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.zip.ZipOutputStream;
public class CreateCourseArchive extends DumbAwareAction {
@@ -57,7 +53,6 @@ public class CreateCourseArchive extends DumbAwareAction {
final CCProjectService service = CCProjectService.getInstance(project);
final Course course = service.getCourse();
if (course == null) return;
generateJson(project);
CreateCourseArchiveDialog dlg = new CreateCourseArchiveDialog(project, this);
dlg.show();
if (dlg.getExitCode() != DialogWrapper.OK_EXIT_CODE) {
@@ -65,7 +60,8 @@ public class CreateCourseArchive extends DumbAwareAction {
}
final VirtualFile baseDir = project.getBaseDir();
final Map<String, Lesson> lessons = course.getLessonsMap();
List<FileEditor> editorList = new ArrayList<FileEditor>();
//List<FileEditor> editorList = new ArrayList<FileEditor>();
Map<VirtualFile, TaskFile> taskFiles = new HashMap<VirtualFile, TaskFile>();
for (Map.Entry<String, Lesson> lesson : lessons.entrySet()) {
final VirtualFile lessonDir = baseDir.findChild(lesson.getKey());
if (lessonDir == null) continue;
@@ -77,17 +73,16 @@ public class CreateCourseArchive extends DumbAwareAction {
if (file == null) continue;
final Document document = FileDocumentManager.getInstance().getDocument(file);
if (document == null) continue;
final TaskFile taskFile = entry.getValue();
for (final TaskWindow taskWindow : taskFile.getTaskWindows()) {
document.addDocumentListener(new InsertionListener(taskFile));
taskFiles.put(file, taskFile);
taskFile.setTrackChanges(false);
Collections.sort(taskFile.getTaskWindows());
for (int i = taskFile.getTaskWindows().size() - 1; i >=0 ; i--) {
final TaskWindow taskWindow = taskFile.getTaskWindows().get(i);
final String taskText = taskWindow.getTaskText();
final int lineStartOffset = document.getLineStartOffset(taskWindow.line);
final int offset = lineStartOffset + taskWindow.start;
final FileEditor[] editors = FileEditorManager.getInstance(project).getEditors(file);
if (editors.length > 0) {
editorList.add(editors[0]);
}
CommandProcessor.getInstance().executeCommand(project, new Runnable() {
@Override
public void run() {
@@ -104,6 +99,7 @@ public class CreateCourseArchive extends DumbAwareAction {
}
}
}
generateJson(project);
try {
File zipFile = new File(myLocationDir, myZipName + ".zip");
ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)));
@@ -124,10 +120,30 @@ public class CreateCourseArchive extends DumbAwareAction {
LOG.error(e1);
}
for (FileEditor fileEditor : editorList) {
UndoManager.getInstance(project).undo(fileEditor);
for (Map.Entry<VirtualFile, TaskFile> entry: taskFiles.entrySet()) {
TaskFile value = entry.getValue();
final Document document = FileDocumentManager.getInstance().getDocument(entry.getKey());
if (document == null) {
continue;
}
for (final TaskWindow taskWindow : value.getTaskWindows()){
final int lineStartOffset = document.getLineStartOffset(taskWindow.line);
final int offset = lineStartOffset + taskWindow.start;
CommandProcessor.getInstance().executeCommand(project, new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
document.replaceString(offset, offset + taskWindow.length, taskWindow.getPossibleAnswer());
FileDocumentManager.getInstance().saveDocument(document);
}
});
}
}, "x", "qwe");
}
value.setTrackChanges(true);
}
VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
ProjectView.getInstance(project).refresh();
}
@@ -162,4 +178,21 @@ public class CreateCourseArchive extends DumbAwareAction {
}
}
}
private class InsertionListener extends StudyDocumentListener {
public InsertionListener(TaskFile taskFile) {
super(taskFile);
}
@Override
protected void updateTaskWindowLength(CharSequence fragment, TaskWindow taskWindow, int change) {
//we don't need to update task window length
}
@Override
protected boolean needModify() {
return true;
}
}
}
@@ -1,7 +1,11 @@
package org.jetbrains.plugins.coursecreator.format;
import com.google.gson.annotations.Expose;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.LogicalPosition;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.coursecreator.CCProjectService;
import java.util.ArrayList;
import java.util.List;
@@ -9,6 +13,15 @@ import java.util.List;
public class TaskFile {
@Expose public List<TaskWindow> task_windows = new ArrayList<TaskWindow>();
public int myIndex;
public boolean myTrackChanges = true;
public boolean isTrackChanges() {
return myTrackChanges;
}
public void setTrackChanges(boolean trackChanges) {
myTrackChanges = trackChanges;
}
public TaskFile() {}
@@ -25,7 +38,74 @@ public class TaskFile {
myIndex = index;
}
public int getIndex() {
return myIndex;
/**
* @param pos position in editor
* @return task window located in specified position or null if there is no task window in this position
*/
@Nullable
public TaskWindow getTaskWindow(@NotNull final Document document, @NotNull final LogicalPosition pos) {
int line = pos.line;
if (line >= document.getLineCount()) {
return null;
}
int column = pos.column;
int offset = document.getLineStartOffset(line) + column;
for (TaskWindow tw : task_windows) {
if (tw.getLine() <= line) {
int twStartOffset = tw.getRealStartOffset(document);
final int length = tw.getReplacementLength() > 0 ? tw.getReplacementLength() : 0;
int twEndOffset = twStartOffset + length;
if (twStartOffset <= offset && offset <= twEndOffset) {
return tw;
}
}
}
return null;
}
/**
* Updates task window lines
*
* @param startLine lines greater than this line and including this line will be updated
* @param change change to be added to line numbers
*/
public void incrementLines(int startLine, int change) {
for (TaskWindow taskTaskWindow : task_windows) {
if (taskTaskWindow.getLine() >= startLine) {
taskTaskWindow.setLine(taskTaskWindow.getLine() + change);
}
}
}
/**
* Updates windows in specific line
*
* @param lineChange change in line number
* @param line line to be updated
* @param newEndOffsetInLine distance from line start to end of inserted fragment
* @param oldEndOffsetInLine distance from line start to end of changed fragment
*/
public void updateLine(int lineChange, int line, int newEndOffsetInLine, int oldEndOffsetInLine) {
for (TaskWindow w : task_windows) {
if ((w.getLine() == line) && (w.getStart() >= oldEndOffsetInLine)) {
int distance = w.getStart() - oldEndOffsetInLine;
boolean coveredByPrevTW = false;
int prevIndex = w.getIndex() - 1;
if (CCProjectService.indexIsValid(prevIndex, task_windows)) {
TaskWindow prevTW = task_windows.get(prevIndex);
if (prevTW.getLine() == line) {
int endOffset = prevTW.getStart() + prevTW.getLength();
if (endOffset >= newEndOffsetInLine) {
coveredByPrevTW = true;
}
}
}
if (lineChange != 0 || newEndOffsetInLine <= w.getStart() || coveredByPrevTW) {
w.setStart(distance + newEndOffsetInLine);
w.setLine(line + lineChange);
}
}
}
}
}
@@ -1,11 +1,13 @@
package org.jetbrains.plugins.coursecreator.format;
import com.google.gson.annotations.Expose;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.markup.HighlighterLayer;
import com.intellij.openapi.editor.markup.HighlighterTargetArea;
import com.intellij.openapi.editor.markup.RangeHighlighter;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
@@ -14,7 +16,7 @@ import org.jetbrains.plugins.coursecreator.CCProjectService;
import java.io.File;
public class TaskWindow {
public class TaskWindow implements Comparable{
@Expose public int line;
@Expose public int start;
@@ -71,8 +73,11 @@ public class TaskWindow {
int endOffset = startOffset + myReplacementLength;
TextAttributes defaultTestAttributes =
EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.LIVE_TEMPLATE_ATTRIBUTES);
editor.getMarkupModel().addRangeHighlighter(startOffset, endOffset, HighlighterLayer.LAST + 1, defaultTestAttributes,
HighlighterTargetArea.EXACT_RANGE);
RangeHighlighter highlighter =
editor.getMarkupModel().addRangeHighlighter(startOffset, endOffset, HighlighterLayer.LAST + 1, defaultTestAttributes,
HighlighterTargetArea.EXACT_RANGE);
highlighter.setGreedyToLeft(true);
highlighter.setGreedyToRight(true);
}
public int getIndex() {
@@ -83,4 +88,46 @@ public class TaskWindow {
myIndex = index;
}
public void setReplacementLength(int replacementLength) {
myReplacementLength = replacementLength;
}
public int getLine() {
return line;
}
public int getRealStartOffset(Document document) {
return document.getLineStartOffset(line) + start;
}
public void setLine(int line) {
this.line = line;
}
public int getStart() {
return start;
}
public void setStart(int start) {
this.start = start;
}
@Override
public int compareTo(Object o) {
TaskWindow taskWindow = (TaskWindow)o;
int lineDiff = line - taskWindow.line;
if (lineDiff == 0) {
return start - taskWindow.start;
}
return lineDiff;
}
public String getPossibleAnswer() {
return possible_answer;
}
public int getLength() {
return length;
}
}