/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.ide.editor.synchronization; import com.google.inject.Inject; import com.google.web.bindery.event.shared.EventBus; import com.google.web.bindery.event.shared.HandlerRegistration; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.ide.api.editor.EditorPartPresenter; import org.eclipse.che.ide.api.editor.EditorWithAutoSave; import org.eclipse.che.ide.api.editor.document.Document; import org.eclipse.che.ide.api.editor.document.DocumentHandle; import org.eclipse.che.ide.api.editor.document.DocumentStorage; import org.eclipse.che.ide.api.editor.events.DocumentChangeEvent; import org.eclipse.che.ide.api.editor.events.DocumentChangeHandler; import org.eclipse.che.ide.api.editor.text.TextPosition; import org.eclipse.che.ide.api.editor.texteditor.TextEditor; import org.eclipse.che.ide.api.event.FileContentUpdateEvent; import org.eclipse.che.ide.api.event.FileContentUpdateHandler; import org.eclipse.che.ide.api.notification.NotificationManager; import org.eclipse.che.ide.api.resources.File; import org.eclipse.che.ide.api.resources.VirtualFile; import javax.validation.constraints.NotNull; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Set; import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.EMERGE_MODE; import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL; import static org.eclipse.che.ide.api.notification.StatusNotification.Status.SUCCESS; public class EditorGroupSynchronizationImpl implements EditorGroupSynchronization, DocumentChangeHandler, FileContentUpdateHandler { private final DocumentStorage documentStorage; private final NotificationManager notificationManager; private final HandlerRegistration fileContentUpdateHandlerRegistration; private final Map<EditorPartPresenter, HandlerRegistration> synchronizedEditors = new HashMap<>(); private EditorPartPresenter groupLeaderEditor; @Inject EditorGroupSynchronizationImpl(EventBus eventBus, DocumentStorage documentStorage, NotificationManager notificationManager) { this.documentStorage = documentStorage; this.notificationManager = notificationManager; fileContentUpdateHandlerRegistration = eventBus.addHandler(FileContentUpdateEvent.TYPE, this); } @Override public void addEditor(EditorPartPresenter editor) { DocumentHandle documentHandle = getDocumentHandleFor(editor); if (documentHandle != null) { HandlerRegistration handlerRegistration = documentHandle.getDocEventBus().addHandler(DocumentChangeEvent.TYPE, this); synchronizedEditors.put(editor, handlerRegistration); } } @Override public void onActiveEditorChanged(@NotNull EditorPartPresenter activeEditor) { groupLeaderEditor = activeEditor; resolveAutoSave(); } @Override public void removeEditor(EditorPartPresenter editor) { if (editor.isDirty()) { editor.doSave(); } HandlerRegistration handlerRegistration = synchronizedEditors.remove(editor); if (handlerRegistration != null) { handlerRegistration.removeHandler(); } if (groupLeaderEditor == editor) { groupLeaderEditor = null; } } @Override public void unInstall() { for (HandlerRegistration handlerRegistration : synchronizedEditors.values()) { handlerRegistration.removeHandler(); } if (fileContentUpdateHandlerRegistration != null) { fileContentUpdateHandlerRegistration.removeHandler(); } groupLeaderEditor = null; } @Override public Set<EditorPartPresenter> getSynchronizedEditors() { return synchronizedEditors.keySet(); } @Override public void onDocumentChange(DocumentChangeEvent event) { DocumentHandle activeEditorDocumentHandle = getDocumentHandleFor(groupLeaderEditor); if (activeEditorDocumentHandle == null || !event.getDocument().isSameAs(activeEditorDocumentHandle)) { return; } for (EditorPartPresenter editor : synchronizedEditors.keySet()) { if (editor == groupLeaderEditor) { continue; } DocumentHandle documentHandle = getDocumentHandleFor(editor); if (documentHandle != null) { documentHandle.getDocument().replace(event.getOffset(), event.getRemoveCharCount(), event.getText()); } } } @Override public void onFileContentUpdate(final FileContentUpdateEvent event) { if (synchronizedEditors.keySet().isEmpty()) { return; } if (groupLeaderEditor == null) { groupLeaderEditor = synchronizedEditors.keySet().iterator().next(); resolveAutoSave(); } final VirtualFile virtualFile = groupLeaderEditor.getEditorInput().getFile(); if (!event.getFilePath().equals(virtualFile.getLocation().toString())) { return; } documentStorage.getDocument(virtualFile, new DocumentStorage.DocumentCallback() { @Override public void onDocumentReceived(final String content) { updateContent(content, event.getModificationStamp(), virtualFile); } @Override public void onDocumentLoadFailure(final Throwable caught) { notificationManager.notify("", "Can not to update content for the file " + virtualFile.getDisplayName(), FAIL, EMERGE_MODE); } }); } private void updateContent(String newContent, String oldStamp, VirtualFile virtualFile) { final DocumentHandle documentHandle = getDocumentHandleFor(groupLeaderEditor); if (documentHandle == null) { return; } final Document document = documentHandle.getDocument(); final String oldContent = document.getContents(); final TextPosition cursorPosition = document.getCursorPosition(); if (!(virtualFile instanceof File)){ replaceContent(document, newContent, oldContent, cursorPosition); return; } final File file = (File)virtualFile; final String newStamp = file.getModificationStamp(); if (oldStamp == null && !Objects.equals(newContent, oldContent)) { replaceContent(document, newContent, oldContent, cursorPosition); return; } if (!Objects.equals(oldStamp, newStamp)) { replaceContent(document, newContent, oldContent, cursorPosition); notificationManager.notify("External operation", "File '" + file.getName() + "' is updated", SUCCESS, EMERGE_MODE); } } private void replaceContent(Document document, String newContent, String oldContent, TextPosition cursorPosition) { document.replace(0, oldContent.length(), newContent); document.setCursorPosition(cursorPosition); } @Nullable private DocumentHandle getDocumentHandleFor(EditorPartPresenter editor) { if (editor == null || !(editor instanceof TextEditor)) { return null; } return ((TextEditor)editor).getDocument().getDocumentHandle(); } private void resolveAutoSave() { for (EditorPartPresenter editor : synchronizedEditors.keySet()) { resolveAutoSaveFor(editor); } } private void resolveAutoSaveFor(EditorPartPresenter editor) { if (!(editor instanceof EditorWithAutoSave)) { return; } EditorWithAutoSave editorWithAutoSave = (EditorWithAutoSave)editor; if (editorWithAutoSave == groupLeaderEditor) { editorWithAutoSave.enableAutoSave(); return; } if (editorWithAutoSave.isAutoSaveEnabled()) { editorWithAutoSave.disableAutoSave(); } } }