From 1c7818f14ee4214cff555811f23cf5e6565b9525 Mon Sep 17 00:00:00 2001 From: Roman Nikitenko Date: Tue, 30 May 2017 23:49:14 +0300 Subject: [PATCH] CHE-3570. Add ability to turn on/off autosave mode for editor content Signed-off-by: Roman Nikitenko --- .../api/editor/AbstractEditorPresenter.java | 2 +- .../che/ide/api/editor/EditorAgent.java | 2 +- .../editor/EditorLocalizationConstants.java | 3 + .../ide/api/editor/autosave/AutoSaveMode.java | 65 ++++ .../AbstractTextEditorProvider.java | 6 +- .../AutoSaveTextEditorConfiguration.java | 2 +- .../DefaultTextEditorConfiguration.java | 12 +- .../editor/reconciler/DefaultReconciler.java | 103 ++++++ .../editor/reconciler/DirtyRegionQueue.java | 4 +- .../reconciler/ReconcilingStrategy.java | 7 +- .../eclipse/che/ide/api/parts/EditorTab.java | 10 +- .../che/ide/api/resources/Resource.java | 2 + .../che/ide/api/resources/VirtualFile.java | 2 - .../core/StandardComponentInitializer.java | 25 +- .../che/ide/editor/EditorAgentImpl.java | 20 +- .../che/ide/editor/EditorApiModule.java | 12 +- .../ide/editor/autosave/AutoSaveModeImpl.java | 274 ++++++++++++++ .../EditorPreferencePresenter.java | 41 +-- .../preferences/EditorPreferenceView.java | 4 +- .../preferences/EditorPreferenceViewImpl.java | 17 +- .../EditorPreferenceViewImpl.ui.xml | 3 +- .../preferences/EditorPreferencesManager.java | 252 +++++++++++++ .../preferences/EditorPreferencesModule.java | 6 + .../editorproperties/EditorProperties.java | 3 +- .../EditorPropertiesManager.java | 3 + .../EditorPropertiesSectionPresenter.java | 63 ++-- .../EditorPropertiesSectionViewImpl.java | 10 +- .../sections/EditPropertiesSection.java | 4 +- .../keymaps/KeymapsPreferenceViewImpl.ui.xml | 6 +- .../EditorGroupSynchronizationImpl.java | 40 ++- .../EditorWorkingCopySynchronizer.java | 33 ++ .../EditorWorkingCopySynchronizerImpl.java | 85 +++++ .../part/editor/EditorPartStackPresenter.java | 14 +- .../widgets/editortab/EditorTabWidget.java | 9 + .../widgets/editortab/EditorTabWidget.ui.xml | 9 + .../ResolvingProjectStateHolderRegistry.java | 2 +- .../general/AbstractPerspective.java | 1 - .../EditorGroupSynchronizationImplTest.java | 38 +- .../orion/client/OrionEditorBuilder.java | 4 +- .../editor/orion/client/OrionEditorInit.java | 14 +- .../orion/client/OrionEditorPresenter.java | 52 +-- .../orion/client/OrionEditorWidget.java | 29 +- .../orion/client/OrionSettingsController.java | 101 ++++++ .../che/ide/editor/orion/OrionEditor.gwt.xml | 1 + .../org-eclipse-jdt-ui/pom.xml | 16 + .../che/jdt/javaeditor/JavaReconciler.java | 337 ++++++++++++++--- .../che-plugin-java-ext-lang-client/pom.xml | 8 + .../java/client/JavaLocalizationConstant.java | 6 + .../client/editor/JavaReconcileClient.java | 40 ++- .../editor/JavaReconcileUpdateOperation.java | 62 ++++ .../client/editor/JavaReconcilerStrategy.java | 83 +++-- .../client/editor/JsJavaEditorProvider.java | 17 - .../editor/ReconcileOperationEvent.java | 52 +++ .../client/inject/JavaEditorGinModule.java | 12 +- .../client/refactoring/move/MoveAction.java | 52 ++- .../rename/RenameRefactoringAction.java | 47 ++- .../JavaLocalizationConstant.properties | 2 + .../editor/JavaReconcilerStrategyTest.java | 56 ++- .../server/JavaReconcileRequestHandler.java | 60 ++++ .../java/server/inject/JdtGuiceModule.java | 5 +- .../plugin/java/server/che/ReconcileTest.java | 10 +- .../ext/java/shared/dto/JavaClassInfo.java | 32 ++ .../che/ide/ext/java/shared/dto/Problem.java | 1 - .../ext/java/shared/dto/ReconcileResult.java | 10 +- .../LanguageServerEditorConfiguration.java | 6 +- .../editor/LanguageServerEditorProvider.java | 4 +- .../ide/editor/LanguageServerFormatter.java | 13 +- .../plugin-maven/che-plugin-maven-ide/pom.xml | 4 + .../client/editor/PomEditorConfiguration.java | 4 +- .../editor/PomReconcileUpdateOperation.java | 63 ++++ .../client/editor/PomReconcilingStrategy.java | 25 +- .../maven/client/inject/MavenGinModule.java | 3 + .../service/MavenServerServiceClientImpl.java | 2 - .../core/project/PomChangeListener.java | 12 +- .../server/core/reconcile/PomReconciler.java | 245 +++++++++++++ .../maven/server/rest/MavenServerService.java | 90 +---- .../reconcile}/PomReconcilerTest.java | 67 ++-- .../html/editor/HtmlEditorConfiguration.java | 4 +- .../web/js/editor/JsEditorConfiguration.java | 8 +- .../ide/editor/JsonExampleEditorProvider.java | 3 - .../project/shared/dto/EditorChangesDto.java | 62 ++++ .../api/project/shared/dto/ServerError.java | 29 ++ .../project/server/EditorChangesTracker.java | 46 +++ .../api/project/server/EditorWorkingCopy.java | 170 +++++++++ .../server/EditorWorkingCopyManager.java | 340 ++++++++++++++++++ .../server/EditorWorkingCopyUpdatedEvent.java | 39 ++ .../che/api/project/server/FileEntry.java | 2 +- .../api/project/server/ProjectApiModule.java | 3 + .../detectors/EditorFileOperationHandler.java | 26 +- 89 files changed, 3096 insertions(+), 477 deletions(-) create mode 100644 ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/autosave/AutoSaveMode.java create mode 100644 ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/reconciler/DefaultReconciler.java create mode 100644 ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/autosave/AutoSaveModeImpl.java create mode 100644 ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/EditorPreferencesManager.java create mode 100644 ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/synchronization/workingCopy/EditorWorkingCopySynchronizer.java create mode 100644 ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/synchronization/workingCopy/EditorWorkingCopySynchronizerImpl.java create mode 100644 ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionSettingsController.java create mode 100644 plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/editor/JavaReconcileUpdateOperation.java create mode 100755 plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/editor/ReconcileOperationEvent.java create mode 100644 plugins/plugin-java/che-plugin-java-ext-lang-server/src/main/java/org/eclipse/che/plugin/java/server/JavaReconcileRequestHandler.java create mode 100644 plugins/plugin-java/che-plugin-java-ext-lang-shared/src/main/java/org/eclipse/che/ide/ext/java/shared/dto/JavaClassInfo.java create mode 100644 plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/editor/PomReconcileUpdateOperation.java create mode 100644 plugins/plugin-maven/che-plugin-maven-server/src/main/java/org/eclipse/che/plugin/maven/server/core/reconcile/PomReconciler.java rename plugins/plugin-maven/che-plugin-maven-server/src/test/java/org/eclipse/che/plugin/maven/server/{ => core/reconcile}/PomReconcilerTest.java (80%) create mode 100644 wsagent/che-core-api-project-shared/src/main/java/org/eclipse/che/api/project/shared/dto/EditorChangesDto.java create mode 100644 wsagent/che-core-api-project-shared/src/main/java/org/eclipse/che/api/project/shared/dto/ServerError.java create mode 100644 wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/EditorChangesTracker.java create mode 100644 wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/EditorWorkingCopy.java create mode 100644 wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/EditorWorkingCopyManager.java create mode 100644 wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/EditorWorkingCopyUpdatedEvent.java diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/AbstractEditorPresenter.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/AbstractEditorPresenter.java index 3dd2a9a2fef..375cf209e1d 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/AbstractEditorPresenter.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/AbstractEditorPresenter.java @@ -13,8 +13,8 @@ import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.IsWidget; -import org.eclipse.che.ide.api.parts.AbstractPartPresenter; import org.eclipse.che.ide.api.editor.EditorAgent.OpenEditorCallback; +import org.eclipse.che.ide.api.parts.AbstractPartPresenter; import javax.validation.constraints.NotNull; import java.util.ArrayList; diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/EditorAgent.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/EditorAgent.java index 22c160195eb..15ae214b380 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/EditorAgent.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/EditorAgent.java @@ -112,7 +112,7 @@ public interface EditorAgent { * * @param callback */ - void saveAll(AsyncCallback callback); + void saveAll(AsyncCallback callback); /** * Current active editor diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/EditorLocalizationConstants.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/EditorLocalizationConstants.java index 8def6f22a4f..884fddec165 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/EditorLocalizationConstants.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/EditorLocalizationConstants.java @@ -64,6 +64,9 @@ public interface EditorLocalizationConstants extends Messages { @DefaultMessage("Soft Wrap") String propertySoftWrap(); + @DefaultMessage("Enable Autosave") + String propertyAutoSave(); + @DefaultMessage("Autopair (Parentheses)") String propertyAutoPairParentheses(); diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/autosave/AutoSaveMode.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/autosave/AutoSaveMode.java new file mode 100644 index 00000000000..00d26f1182d --- /dev/null +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/autosave/AutoSaveMode.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * 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.api.editor.autosave; + +import org.eclipse.che.ide.api.editor.document.UseDocumentHandle; +import org.eclipse.che.ide.api.editor.events.DocumentChangeHandler; +import org.eclipse.che.ide.api.editor.texteditor.TextEditor; + +/** + * Editor content auto save functionality. + * + * @author Roman Nikitenko + */ +public interface AutoSaveMode extends DocumentChangeHandler, UseDocumentHandle { + + /** + * Installs auto save mode on the given editor. + */ + void install(TextEditor editor); + + /** + * Removes auto save mode from editor. + */ + void uninstall(); + + /** + * Suspends auto save mode for editor content. + */ + void suspend(); + + /** + * Resumes auto save mode for editor content and sets mode corresponding to 'Enable Autosave' option in editor preferences. + */ + void resume(); + + /** + * Return true if auto save mode is activated, false otherwise. + */ + boolean isActivated(); + + enum Mode { + /** + * The state when auto save mode of editor content is turned on. + * Corresponds to the case when the 'Enable Autosave' option in editor preferences is enabled. + */ + ACTIVATED, + + /** Corresponds to the state when auto save mode is suspended for processing some operations (java refactoring, for example) */ + SUSPENDED, + + /** + * The state when auto save mode of editor content is turned off. + * Corresponds to the case when the 'Enable Autosave' option in editor preferences is disabled. + */ + DEACTIVATED + } +} diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/defaulteditor/AbstractTextEditorProvider.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/defaulteditor/AbstractTextEditorProvider.java index 39b7a4a4266..d0439b637d8 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/defaulteditor/AbstractTextEditorProvider.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/defaulteditor/AbstractTextEditorProvider.java @@ -13,7 +13,7 @@ import com.google.inject.Inject; import org.eclipse.che.ide.api.editor.EditorProvider; -import org.eclipse.che.ide.api.editor.editorconfig.AutoSaveTextEditorConfiguration; +import org.eclipse.che.ide.api.editor.editorconfig.DefaultTextEditorConfiguration; import org.eclipse.che.ide.api.editor.editorconfig.TextEditorConfiguration; import org.eclipse.che.ide.api.editor.texteditor.TextEditor; import org.eclipse.che.ide.util.loging.Log; @@ -25,7 +25,7 @@ * implementation for the {@link #getId()} and {@link #getDescription()} methods. *

The method {@link #getEditor()} returns {@link TextEditor} * that is initialized by configuration returned by {@link #getEditorConfiguration()} method. - *

The method {@link #getEditorConfiguration()} returns {@link AutoSaveTextEditorConfiguration} + *

The method {@link #getEditorConfiguration()} returns {@link DefaultTextEditorConfiguration} * instance and may be overridden in order to provide another configuration for the editor * which is returned by {@link #getEditor()} method. * @@ -38,7 +38,7 @@ public abstract class AbstractTextEditorProvider implements EditorProvider { /** Returns configuration for initializing an editor returned by {@link #getEditor()} method. */ protected TextEditorConfiguration getEditorConfiguration() { - return new AutoSaveTextEditorConfiguration(); + return new DefaultTextEditorConfiguration(); } @Override diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/editorconfig/AutoSaveTextEditorConfiguration.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/editorconfig/AutoSaveTextEditorConfiguration.java index d10a155f825..c9d64dc5a7e 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/editorconfig/AutoSaveTextEditorConfiguration.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/editorconfig/AutoSaveTextEditorConfiguration.java @@ -17,7 +17,7 @@ /** * @author Evgen Vidolob */ -public class AutoSaveTextEditorConfiguration extends DefaultTextEditorConfiguration{ +public class AutoSaveTextEditorConfiguration extends DefaultTextEditorConfiguration { private ReconcilerWithAutoSave reconcilerWithAutoSave = new ReconcilerWithAutoSave(DocumentPartitioner.DEFAULT_CONTENT_TYPE, getPartitioner()); diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/editorconfig/DefaultTextEditorConfiguration.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/editorconfig/DefaultTextEditorConfiguration.java index a6c054ec0e6..aa03d39c333 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/editorconfig/DefaultTextEditorConfiguration.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/editorconfig/DefaultTextEditorConfiguration.java @@ -18,16 +18,20 @@ import org.eclipse.che.ide.api.editor.partition.DocumentPartitioner; import org.eclipse.che.ide.api.editor.partition.DocumentPositionMap; import org.eclipse.che.ide.api.editor.quickfix.QuickAssistProcessor; +import org.eclipse.che.ide.api.editor.reconciler.DefaultReconciler; import org.eclipse.che.ide.api.editor.reconciler.Reconciler; import org.eclipse.che.ide.api.editor.signature.SignatureHelpProvider; import java.util.Map; +import static org.eclipse.che.ide.api.editor.partition.DocumentPartitioner.DEFAULT_CONTENT_TYPE; + /** * Default implementation of the {@link TextEditorConfiguration}. */ public class DefaultTextEditorConfiguration implements TextEditorConfiguration { + private DefaultReconciler reconciler; private ConstantPartitioner partitioner; @Override @@ -47,15 +51,13 @@ public Map getContentAssistantProcessors() { @Override public Reconciler getReconciler() { - return null; + return reconciler == null ? reconciler = new DefaultReconciler(DEFAULT_CONTENT_TYPE, getPartitioner()) : reconciler; + } @Override public DocumentPartitioner getPartitioner() { - if(partitioner == null) { - partitioner = new ConstantPartitioner(); - } - return partitioner; + return partitioner == null ? partitioner = new ConstantPartitioner() : partitioner; } @Override diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/reconciler/DefaultReconciler.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/reconciler/DefaultReconciler.java new file mode 100644 index 00000000000..bf9359f0bca --- /dev/null +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/reconciler/DefaultReconciler.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * 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.api.editor.reconciler; + +import com.google.inject.assistedinject.Assisted; +import com.google.inject.assistedinject.AssistedInject; + +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.events.DocumentChangeEvent; +import org.eclipse.che.ide.api.editor.partition.DocumentPartitioner; +import org.eclipse.che.ide.api.editor.texteditor.TextEditor; + +import java.util.HashMap; +import java.util.Map; + +/** + * Default implementation of {@link Reconciler}. + * + * @author Roman Nikitenko + */ +public class DefaultReconciler implements Reconciler { + + private final String partition; + private final DocumentPartitioner partitioner; + private final Map strategies; + + private DocumentHandle documentHandle; + private TextEditor editor; + + + @AssistedInject + public DefaultReconciler(@Assisted final String partition, + @Assisted final DocumentPartitioner partitioner) { + this.partition = partition; + this.partitioner = partitioner; + strategies = new HashMap<>(); + } + + @Override + public void install(TextEditor editor) { + this.editor = editor; + reconcilerDocumentChanged(); + } + + @Override + public void uninstall() { + strategies.values().forEach(ReconcilingStrategy::closeReconciler); + } + + @Override + public ReconcilingStrategy getReconcilingStrategy(final String contentType) { + return strategies.get(contentType); + } + + @Override + public void addReconcilingStrategy(final String contentType, final ReconcilingStrategy strategy) { + strategies.put(contentType, strategy); + } + + @Override + public String getDocumentPartitioning() { + return partition; + } + + @Override + public void onDocumentChange(final DocumentChangeEvent event) { + } + + @Override + public DocumentHandle getDocumentHandle() { + return this.documentHandle; + } + + @Override + public void setDocumentHandle(final DocumentHandle handle) { + this.documentHandle = handle; + } + + /** + * Returns the input document of the text view this reconciler is installed on. + * + * @return the reconciler document + */ + protected Document getDocument() { + return documentHandle.getDocument(); + } + + private void reconcilerDocumentChanged() { + strategies.keySet().forEach(key -> { + ReconcilingStrategy reconcilingStrategy = strategies.get(key); + reconcilingStrategy.setDocument(documentHandle.getDocument()); + }); + } +} diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/reconciler/DirtyRegionQueue.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/reconciler/DirtyRegionQueue.java index 8ed26b323e0..389f7df7447 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/reconciler/DirtyRegionQueue.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/reconciler/DirtyRegionQueue.java @@ -14,10 +14,10 @@ import java.util.List; /** - * Queue used by {@link ReconcilerWithAutoSave} to manage dirty regions. When a dirty region is inserted into the queue, the queue tries to fold it + * Queue used to manage dirty regions. When a dirty region is inserted into the queue, the queue tries to fold it * into the neighboring dirty region. */ -class DirtyRegionQueue { +public class DirtyRegionQueue { /** The list of dirty regions. */ private final List fDirtyRegions = new ArrayList<>(); diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/reconciler/ReconcilingStrategy.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/reconciler/ReconcilingStrategy.java index ce46b816310..a25dd9968e0 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/reconciler/ReconcilingStrategy.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/editor/reconciler/ReconcilingStrategy.java @@ -17,8 +17,7 @@ * A reconciling strategy is used by an reconciler to reconcile a model * based on text of a particular content type. * - * @author Evgen Vidolob - * @version $Id: + * @author Evgen Vidolob */ public interface ReconcilingStrategy { /** @@ -38,7 +37,7 @@ public interface ReconcilingStrategy { * As a dirty region might span multiple content types, the segment of the * dirty region which should be investigated is also provided to this * reconciling strategy. The given regions refer to the document passed into - * the most recent call of {@link #setDocument(org.eclipse.che.ide.api.editor.document.Document)}. + * the most recent call of {@link #setDocument(Document)}. * * @param dirtyRegion * the document region which has been changed @@ -50,7 +49,7 @@ public interface ReconcilingStrategy { /** * Activates non-incremental reconciling. The reconciling strategy is just told * that there are changes and that it should reconcile the given partition of the - * document most recently passed into {@link #setDocument(org.eclipse.che.ide.api.editor.document.Document)}. + * document most recently passed into {@link #setDocument(Document)}. * * @param partition * the document partition to be reconciled diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/parts/EditorTab.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/parts/EditorTab.java index 0a27ae64537..88bf69c0796 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/parts/EditorTab.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/parts/EditorTab.java @@ -10,8 +10,6 @@ *******************************************************************************/ package org.eclipse.che.ide.api.parts; -import com.google.gwt.event.dom.client.DoubleClickHandler; - import org.eclipse.che.ide.api.editor.EditorPartPresenter; import org.eclipse.che.ide.api.mvp.View; import org.eclipse.che.ide.api.parts.PartStackView.TabItem; @@ -54,6 +52,14 @@ public interface EditorTab extends View, TabItem { */ EditorPartPresenter getRelativeEditorPart(); + /** + * Set unsaved data mark to editor tab item. + * + * @param hasUnsavedData + * true if tab should display 'unsaved data' mark, otherwise false + */ + void setUnsavedDataMark(boolean hasUnsavedData); + /** * Set pin mark to editor tab item. * diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/resources/Resource.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/resources/Resource.java index d0a039538ce..5a075633723 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/resources/Resource.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/resources/Resource.java @@ -14,6 +14,7 @@ import com.google.common.base.Optional; import org.eclipse.che.api.promises.client.Promise; +import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.ide.api.app.AppContext; import org.eclipse.che.ide.api.resources.Project.ProjectRequest; import org.eclipse.che.ide.api.resources.marker.Marker; @@ -478,6 +479,7 @@ public interface Resource extends Comparable { * @return the bound instance of {@link Project} or null * @since 5.1.0 */ + @Nullable Project getProject(); /** diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/resources/VirtualFile.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/resources/VirtualFile.java index 416233c0b6a..86e0914549d 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/resources/VirtualFile.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/resources/VirtualFile.java @@ -10,8 +10,6 @@ *******************************************************************************/ package org.eclipse.che.ide.api.resources; -import com.google.common.annotations.Beta; - import org.eclipse.che.api.promises.client.Promise; import org.eclipse.che.ide.resource.Path; diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/core/StandardComponentInitializer.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/core/StandardComponentInitializer.java index a7032d7880d..5c298790c13 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/core/StandardComponentInitializer.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/core/StandardComponentInitializer.java @@ -163,6 +163,7 @@ public class StandardComponentInitializer { public static final String PREVIEW_IMAGE = "previewImage"; public static final String FIND_ACTION = "findAction"; public static final String FORMAT = "format"; + public static final String SAVE = "save"; public static final String COPY = "copy"; public static final String CUT = "cut"; public static final String PASTE = "paste"; @@ -602,6 +603,12 @@ public void initialize() { actionManager.registerAction("projectConfiguration", projectConfigurationAction); projectGroup.add(projectConfigurationAction); + DefaultActionGroup saveGroup = new DefaultActionGroup(actionManager); + actionManager.registerAction("saveGroup", saveGroup); + actionManager.registerAction(SAVE, saveAction); + saveGroup.addSeparator(); + saveGroup.add(saveAction); + // Edit (New Menu) DefaultActionGroup editGroup = (DefaultActionGroup)actionManager.getAction(GROUP_EDIT); DefaultActionGroup recentGroup = new DefaultActionGroup(RECENT_GROUP_ID, true, actionManager); @@ -613,14 +620,14 @@ public void initialize() { actionManager.registerAction(OPEN_RECENT_FILES, openRecentFilesAction); editGroup.add(openRecentFilesAction); - editGroup.addSeparator(); - actionManager.registerAction(CLOSE_ACTIVE_EDITOR, closeActiveEditorAction); editGroup.add(closeActiveEditorAction); actionManager.registerAction(FORMAT, formatterAction); editGroup.add(formatterAction); + editGroup.add(saveAction); + actionManager.registerAction("undo", undoAction); editGroup.add(undoAction); @@ -676,15 +683,6 @@ public void initialize() { actionManager.registerAction(NAVIGATE_TO_FILE, navigateToFileAction); assistantGroup.add(navigateToFileAction); - // Compose Save group - DefaultActionGroup saveGroup = new DefaultActionGroup(actionManager); - actionManager.registerAction("saveGroup", saveGroup); - actionManager.registerAction("save", saveAction); - actionManager.registerAction("saveAll", saveAllAction); - saveGroup.addSeparator(); - saveGroup.add(saveAction); - saveGroup.add(saveAllAction); - //Compose Profile menu DefaultActionGroup profileGroup = (DefaultActionGroup)actionManager.getAction(GROUP_PROFILE); actionManager.registerAction("showPreferences", showPreferencesAction); @@ -711,6 +709,7 @@ public void initialize() { resourceOperation.add(goIntoAction); resourceOperation.add(editFileAction); + resourceOperation.add(saveAction); resourceOperation.add(cutResourceAction); resourceOperation.add(copyResourceAction); resourceOperation.add(pasteResourceAction); @@ -760,6 +759,7 @@ public void initialize() { DefaultActionGroup mainToolbarGroup = (DefaultActionGroup)actionManager.getAction(GROUP_MAIN_TOOLBAR); mainToolbarGroup.add(newGroup); + mainToolbarGroup.add(saveGroup); mainToolbarGroup.add(changeResourceGroup); toolbarPresenter.bindMainGroup(mainToolbarGroup); @@ -807,6 +807,7 @@ public void initialize() { DefaultActionGroup editorContextMenuGroup = new DefaultActionGroup(actionManager); actionManager.registerAction(GROUP_EDITOR_CONTEXT_MENU, editorContextMenuGroup); + editorContextMenuGroup.add(saveAction); editorContextMenuGroup.add(undoAction); editorContextMenuGroup.add(redoAction); editorContextMenuGroup.addSeparator(); @@ -840,6 +841,8 @@ public void initialize() { keyBinding.getGlobal().addKey(new KeyBuilder().shift().charCode(KeyCodeMap.F10).build(), SHOW_COMMANDS_PALETTE); + keyBinding.getGlobal().addKey(new KeyBuilder().action().charCode('s').build(), SAVE); + if (UserAgent.isMac()) { keyBinding.getGlobal().addKey(new KeyBuilder().control().charCode('w').build(), CLOSE_ACTIVE_EDITOR); keyBinding.getGlobal().addKey(new KeyBuilder().control().charCode('p').build(), SIGNATURE_HELP); diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/EditorAgentImpl.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/EditorAgentImpl.java index 7af06218d9f..961bc2b6883 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/EditorAgentImpl.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/EditorAgentImpl.java @@ -145,7 +145,17 @@ public EditorAgentImpl(EventBus eventBus, @Override public void onClose(EditorPartPresenter editor) { - closeEditor(editor); + if (editor == null) { + return; + } + + final EditorPartStack editorPartStack = editorMultiPartStack.getPartStackByPart(editor); + if (editorPartStack == null) { + return; + } + + EditorTab editorTab = editorPartStack.getTabByPart(editor); + doCloseEditor(editorTab); } @Override @@ -348,16 +358,16 @@ public EditorPartPresenter getOpenedEditor(Path path) { /** {@inheritDoc} */ @Override - public void saveAll(final AsyncCallback callback) { + public void saveAll(final AsyncCallback callback) { dirtyEditors = getDirtyEditors(); if (dirtyEditors.isEmpty()) { - callback.onSuccess("Success"); + callback.onSuccess(null); } else { doSave(callback); } } - private void doSave(final AsyncCallback callback) { + private void doSave(final AsyncCallback callback) { final EditorPartPresenter partPresenter = dirtyEditors.get(0); partPresenter.doSave(new AsyncCallback() { @Override @@ -369,7 +379,7 @@ public void onFailure(Throwable caught) { public void onSuccess(EditorInput result) { dirtyEditors.remove(partPresenter); if (dirtyEditors.isEmpty()) { - callback.onSuccess("Success"); + callback.onSuccess(null); } else { doSave(callback); } diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/EditorApiModule.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/EditorApiModule.java index c48fea61cb1..b85b3200c04 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/EditorApiModule.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/EditorApiModule.java @@ -18,6 +18,7 @@ import org.eclipse.che.ide.api.editor.EditorAgent; import org.eclipse.che.ide.api.editor.EditorProvider; import org.eclipse.che.ide.api.editor.EditorRegistry; +import org.eclipse.che.ide.api.editor.autosave.AutoSaveMode; import org.eclipse.che.ide.api.editor.codeassist.CodeAssistant; import org.eclipse.che.ide.api.editor.codeassist.CodeAssistantFactory; import org.eclipse.che.ide.api.editor.codeassist.CodeAssistantImpl; @@ -29,16 +30,19 @@ import org.eclipse.che.ide.api.editor.partition.DocumentPositionMapImpl; import org.eclipse.che.ide.api.editor.quickfix.QuickAssistAssistant; import org.eclipse.che.ide.api.editor.quickfix.QuickAssistantFactory; +import org.eclipse.che.ide.api.editor.reconciler.DefaultReconciler; import org.eclipse.che.ide.api.editor.reconciler.Reconciler; import org.eclipse.che.ide.api.editor.reconciler.ReconcilerFactory; -import org.eclipse.che.ide.api.editor.reconciler.ReconcilerWithAutoSave; import org.eclipse.che.ide.api.editor.texteditor.TextEditorPartView; +import org.eclipse.che.ide.editor.autosave.AutoSaveModeImpl; import org.eclipse.che.ide.editor.quickfix.QuickAssistAssistantImpl; import org.eclipse.che.ide.editor.quickfix.QuickAssistWidgetFactory; import org.eclipse.che.ide.editor.synchronization.EditorContentSynchronizer; import org.eclipse.che.ide.editor.synchronization.EditorContentSynchronizerImpl; import org.eclipse.che.ide.editor.synchronization.EditorGroupSynchronization; import org.eclipse.che.ide.editor.synchronization.EditorGroupSynchronizationImpl; +import org.eclipse.che.ide.editor.synchronization.workingCopy.EditorWorkingCopySynchronizer; +import org.eclipse.che.ide.editor.synchronization.workingCopy.EditorWorkingCopySynchronizerImpl; import org.eclipse.che.ide.editor.texteditor.TextEditorPartViewImpl; import org.eclipse.che.ide.editor.texteditor.infopanel.InfoPanel; import org.eclipse.che.ide.part.editor.EditorPartStackView; @@ -59,6 +63,8 @@ public class EditorApiModule extends AbstractGinModule { protected void configure() { bind(EditorAgent.class).to(EditorAgentImpl.class).in(Singleton.class); + bind(AutoSaveMode.class).to(AutoSaveModeImpl.class); + bind(UserActivityManager.class).in(Singleton.class); bind(EditorRegistry.class).to(EditorRegistryImpl.class).in(Singleton.class); @@ -88,7 +94,7 @@ protected void configure() { // bind the reconciler install(new GinFactoryModuleBuilder() - .implement(Reconciler.class, ReconcilerWithAutoSave.class) + .implement(Reconciler.class, DefaultReconciler.class) .build(ReconcilerFactory.class)); // bind the code assistant and quick assistant @@ -106,5 +112,7 @@ protected void configure() { install(new GinFactoryModuleBuilder().build(RecentFileActionFactory.class)); bind(RecentFileList.class).to(RecentFileStore.class).in(Singleton.class); + + bind(EditorWorkingCopySynchronizer.class).to(EditorWorkingCopySynchronizerImpl.class).in(Singleton.class); } } diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/autosave/AutoSaveModeImpl.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/autosave/AutoSaveModeImpl.java new file mode 100644 index 00000000000..276eda4b19f --- /dev/null +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/autosave/AutoSaveModeImpl.java @@ -0,0 +1,274 @@ +/******************************************************************************* + * 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.autosave; + +import com.google.gwt.user.client.Timer; +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.ide.api.editor.EditorOpenedEvent; +import org.eclipse.che.ide.api.editor.EditorOpenedEventHandler; +import org.eclipse.che.ide.api.editor.EditorPartPresenter; +import org.eclipse.che.ide.api.editor.autosave.AutoSaveMode; +import org.eclipse.che.ide.api.editor.document.DocumentHandle; +import org.eclipse.che.ide.api.editor.events.DocumentChangeEvent; +import org.eclipse.che.ide.api.editor.reconciler.DirtyRegion; +import org.eclipse.che.ide.api.editor.reconciler.DirtyRegionQueue; +import org.eclipse.che.ide.api.editor.texteditor.TextEditor; +import org.eclipse.che.ide.api.event.ActivePartChangedEvent; +import org.eclipse.che.ide.api.event.ActivePartChangedHandler; +import org.eclipse.che.ide.api.event.EditorSettingsChangedEvent; +import org.eclipse.che.ide.api.event.EditorSettingsChangedEvent.EditorSettingsChangedHandler; +import org.eclipse.che.ide.api.parts.PartPresenter; +import org.eclipse.che.ide.api.resources.Project; +import org.eclipse.che.ide.api.resources.Resource; +import org.eclipse.che.ide.api.resources.VirtualFile; +import org.eclipse.che.ide.editor.preferences.EditorPreferencesManager; +import org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties; +import org.eclipse.che.ide.editor.synchronization.workingCopy.EditorWorkingCopySynchronizer; +import org.eclipse.che.ide.util.loging.Log; + +import java.util.HashSet; + +import static org.eclipse.che.ide.api.editor.autosave.AutoSaveMode.Mode.ACTIVATED; +import static org.eclipse.che.ide.api.editor.autosave.AutoSaveMode.Mode.DEACTIVATED; +import static org.eclipse.che.ide.api.editor.autosave.AutoSaveMode.Mode.SUSPENDED; + +/** + * Default implementation of {@link AutoSaveMode} which provides auto save function for editor content. + * + * @author Roman Nikitenko + */ +public class AutoSaveModeImpl implements AutoSaveMode, EditorSettingsChangedHandler, ActivePartChangedHandler, EditorOpenedEventHandler { + private static final int DELAY = 1000; + + private EventBus eventBus; + private EditorPreferencesManager editorPreferencesManager; + private EditorWorkingCopySynchronizer editorWorkingCopySynchronizer; + private DocumentHandle documentHandle; + private TextEditor editor; + private EditorPartPresenter activeEditor; + private DirtyRegionQueue dirtyRegionQueue; + private Mode mode; + + private HashSet handlerRegistrations = new HashSet<>(4); + + private boolean syncLock = false; + + private final Timer saveTimer = new Timer() { + @Override + public void run() { + save(); + } + }; + + @Inject + public AutoSaveModeImpl(EventBus eventBus, + EditorPreferencesManager editorPreferencesManager, + EditorWorkingCopySynchronizer editorWorkingCopySynchronizer) { + this.eventBus = eventBus; + this.editorPreferencesManager = editorPreferencesManager; + this.editorWorkingCopySynchronizer = editorWorkingCopySynchronizer; + + mode = ACTIVATED; //autosave is activated by default + + addHandlers(); + } + + @Override + public DocumentHandle getDocumentHandle() { + return documentHandle; + } + + @Override + public void setDocumentHandle(DocumentHandle documentHandle) { + this.documentHandle = documentHandle; + } + + @Override + public void install(TextEditor editor) { + this.editor = editor; + this.dirtyRegionQueue = new DirtyRegionQueue(); + updateAutoSaveState(); + } + + @Override + public void uninstall() { + saveTimer.cancel(); + handlerRegistrations.forEach(HandlerRegistration::removeHandler); + } + + @Override + public void suspend() { + mode = SUSPENDED; + } + + @Override + public void resume() { + updateAutoSaveState(); + } + + @Override + public boolean isActivated() { + return mode == ACTIVATED; + } + + @Override + public void onEditorSettingsChanged(EditorSettingsChangedEvent event) { + updateAutoSaveState(); + } + + private void updateAutoSaveState() { + Boolean autoSaveValue = editorPreferencesManager.getBooleanValueFor(EditorProperties.ENABLE_AUTO_SAVE); + if (autoSaveValue == null) { + return; + } + + if (DEACTIVATED != mode && !autoSaveValue) { + mode = DEACTIVATED; + saveTimer.cancel(); + } else if (ACTIVATED != mode && autoSaveValue) { + mode = ACTIVATED; + + saveTimer.cancel(); + saveTimer.schedule(DELAY); + } + } + + @Override + public void onActivePartChanged(ActivePartChangedEvent event) { + PartPresenter activePart = event.getActivePart(); + if (activePart instanceof EditorPartPresenter) { + activeEditor = (EditorPartPresenter)activePart; + } + } + + @Override + public void onEditorOpened(EditorOpenedEvent editorOpenedEvent) { + if (documentHandle != null && editor == editorOpenedEvent.getEditor()) { + HandlerRegistration documentChangeHandlerRegistration = + documentHandle.getDocEventBus().addHandler(DocumentChangeEvent.TYPE, this); + handlerRegistrations.add(documentChangeHandlerRegistration); + } + } + + @Override + public void onDocumentChange(final DocumentChangeEvent event) { + if (documentHandle == null || !event.getDocument().isSameAs(documentHandle)) { + return; + } + + if (SUSPENDED == mode && editor != activeEditor) { + return; + } + + createDirtyRegion(event); + + saveTimer.cancel(); + saveTimer.schedule(DELAY); + + } + + /** + * Creates a dirty region for a document event and adds it to the queue. + * + * @param event + * the document event for which to create a dirty region + */ + private void createDirtyRegion(final DocumentChangeEvent event) { + if (event.getRemoveCharCount() == 0 && event.getText() != null && !event.getText().isEmpty()) { + // Insert + dirtyRegionQueue.addDirtyRegion(new DirtyRegion(event.getOffset(), + event.getLength(), + DirtyRegion.INSERT, + event.getText())); + + } else if (event.getText() == null || event.getText().isEmpty()) { + // Remove + dirtyRegionQueue.addDirtyRegion(new DirtyRegion(event.getOffset(), + event.getRemoveCharCount(), + DirtyRegion.REMOVE, + null)); + + } else { + // Replace (Remove + Insert) + dirtyRegionQueue.addDirtyRegion(new DirtyRegion(event.getOffset(), + event.getRemoveCharCount(), + DirtyRegion.REMOVE, + null)); + dirtyRegionQueue.addDirtyRegion(new DirtyRegion(event.getOffset(), + event.getLength(), + DirtyRegion.INSERT, + event.getText())); + } + } + + private void save() { + if (ACTIVATED == mode && editor.isDirty()) { + editor.doSave(); + } + + VirtualFile file = editor.getEditorInput().getFile(); + Project project = getProject(file); + if (project == null) { + return; + } + + String filePath = file.getLocation().toString(); + String projectPath = project.getPath(); + synchronizeWorkingCopy(filePath, projectPath); + } + + private void synchronizeWorkingCopy(String filePath, String projectPath) { + if (syncLock) { + return; + } + + syncLock = true; + + final DirtyRegion region = dirtyRegionQueue.removeNextDirtyRegion(); + if (region == null) { + syncLock = false; + return; + } + + editorWorkingCopySynchronizer.synchronize(filePath, projectPath, region) + .onSuccess(aVoid -> { + syncLock = false; + synchronizeWorkingCopy(filePath, projectPath); + }) + .onFailure(jsonRpcError -> { + syncLock = false; + Log.error(getClass(), jsonRpcError.getMessage()); + }); + } + + private Project getProject(VirtualFile file) { + if (file == null || !(file instanceof Resource)) { + return null; + } + + Project project = ((Resource)file).getProject(); + return (project != null && project.exists()) ? project : null; + } + + private void addHandlers() { + HandlerRegistration activePartChangedHandlerRegistration = eventBus.addHandler(ActivePartChangedEvent.TYPE, this); + handlerRegistrations.add(activePartChangedHandlerRegistration); + + HandlerRegistration editorSettingsChangedHandlerRegistration = eventBus.addHandler(EditorSettingsChangedEvent.TYPE, this); + handlerRegistrations.add(editorSettingsChangedHandlerRegistration); + + HandlerRegistration editorOpenedHandlerRegistration = eventBus.addHandler(EditorOpenedEvent.TYPE, this); + handlerRegistrations.add(editorOpenedHandlerRegistration); + } +} diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/EditorPreferencePresenter.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/EditorPreferencePresenter.java index d78ab2ef4f3..b7679e9589c 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/EditorPreferencePresenter.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/EditorPreferencePresenter.java @@ -15,8 +15,8 @@ import com.google.inject.Singleton; import org.eclipse.che.ide.api.preferences.AbstractPreferencePagePresenter; -import org.eclipse.che.ide.editor.preferences.editorproperties.EditorPropertiesPresenter; -import org.eclipse.che.ide.editor.preferences.keymaps.KeyMapsPreferencePresenter; + +import java.util.Set; /** Preference page presenter for the editors. */ @Singleton @@ -24,35 +24,31 @@ public class EditorPreferencePresenter extends AbstractPreferencePagePresenter i /** The editor preferences page view. */ private final EditorPreferenceView view; - - private final KeyMapsPreferencePresenter keymapsSection; - private final EditorPropertiesPresenter editorPropertiesSection; + private final Set editorPreferenceSections; @Inject public EditorPreferencePresenter(final EditorPreferenceView view, final EditorPrefLocalizationConstant constant, - final KeyMapsPreferencePresenter keymapsSection, - final EditorPropertiesPresenter editorPropertiesSection) { - + final Set editorPreferenceSections) { super(constant.editorTypeTitle(), constant.editorTypeCategory()); this.view = view; - this.keymapsSection = keymapsSection; - this.editorPropertiesSection = editorPropertiesSection; + this.editorPreferenceSections = editorPreferenceSections; - this.keymapsSection.setParent(this); - this.editorPropertiesSection.setParent(this); + editorPreferenceSections.forEach(section -> section.setParent(this)); } @Override public boolean isDirty() { - return keymapsSection.isDirty() || editorPropertiesSection.isDirty(); + return editorPreferenceSections.stream().anyMatch(EditorPreferenceSection::isDirty); } @Override public void go(final AcceptsOneWidget container) { - keymapsSection.go(view.getKeymapsContainer()); - editorPropertiesSection.go(view.getEditorPropertiesContainer()); + AcceptsOneWidget preferencesContainer = view.getEditorPreferencesContainer(); + + editorPreferenceSections.forEach(section -> section.go(preferencesContainer)); + container.setWidget(view); } @@ -63,21 +59,14 @@ public void signalDirtyState() { @Override public void storeChanges() { - if (keymapsSection.isDirty()) { - keymapsSection.storeChanges(); - } - - if (editorPropertiesSection.isDirty()) { - editorPropertiesSection.storeChanges(); - } + editorPreferenceSections.stream() + .filter(EditorPreferenceSection::isDirty) + .forEach(EditorPreferenceSection::storeChanges); } @Override public void revertChanges() { - keymapsSection.refresh(); - editorPropertiesSection.refresh(); - + editorPreferenceSections.stream().forEach(EditorPreferenceSection::refresh); signalDirtyState(); } - } diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/EditorPreferenceView.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/EditorPreferenceView.java index 3f8512a2273..48d3c9ed37d 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/EditorPreferenceView.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/EditorPreferenceView.java @@ -15,7 +15,5 @@ /** View interface for the preference page for the editor preferences. */ public interface EditorPreferenceView extends IsWidget { - - AcceptsOneWidget getKeymapsContainer(); - AcceptsOneWidget getEditorPropertiesContainer(); + AcceptsOneWidget getEditorPreferencesContainer(); } diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/EditorPreferenceViewImpl.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/EditorPreferenceViewImpl.java index e47b3dbcc9f..dba011cccf1 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/EditorPreferenceViewImpl.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/EditorPreferenceViewImpl.java @@ -15,6 +15,7 @@ import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.ui.AcceptsOneWidget; import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.ScrollPanel; import com.google.gwt.user.client.ui.SimplePanel; import com.google.inject.Inject; @@ -26,9 +27,7 @@ public class EditorPreferenceViewImpl extends Composite implements EditorPrefere private static final EditorPreferenceViewImplUiBinder UIBINDER = GWT.create(EditorPreferenceViewImplUiBinder.class); @UiField - SimplePanel keymapsSection; - @UiField - SimplePanel editorPropertiesSection; + FlowPanel editorPreferencesContainer; @Inject public EditorPreferenceViewImpl() { @@ -36,17 +35,13 @@ public EditorPreferenceViewImpl() { } @Override - public AcceptsOneWidget getKeymapsContainer() { - return this.keymapsSection; - } - - @Override - public AcceptsOneWidget getEditorPropertiesContainer() { - return editorPropertiesSection; + public AcceptsOneWidget getEditorPreferencesContainer() { + SimplePanel container = new SimplePanel(); + editorPreferencesContainer.add(container); + return container; } /** UI binder interface for the {@link EditorPreferenceViewImpl} component. */ interface EditorPreferenceViewImplUiBinder extends UiBinder { } - } diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/EditorPreferenceViewImpl.ui.xml b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/EditorPreferenceViewImpl.ui.xml index 0dd52b584d1..58493d91bb3 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/EditorPreferenceViewImpl.ui.xml +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/EditorPreferenceViewImpl.ui.xml @@ -19,8 +19,7 @@ - - + diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/EditorPreferencesManager.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/EditorPreferencesManager.java new file mode 100644 index 00000000000..635a6797049 --- /dev/null +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/EditorPreferencesManager.java @@ -0,0 +1,252 @@ +/******************************************************************************* + * 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.preferences; + +import com.google.gwt.json.client.JSONBoolean; +import com.google.gwt.json.client.JSONNumber; +import com.google.gwt.json.client.JSONObject; +import com.google.gwt.json.client.JSONParser; +import com.google.gwt.json.client.JSONValue; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import org.eclipse.che.commons.annotation.Nullable; +import org.eclipse.che.ide.api.editor.EditorLocalizationConstants; +import org.eclipse.che.ide.api.preferences.PreferencesManager; +import org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties; + +import javax.validation.constraints.NotNull; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; + +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.AUTO_COMPLETE_COMMENTS; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.AUTO_PAIR_ANGLE_BRACKETS; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.AUTO_PAIR_BRACES; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.AUTO_PAIR_PARENTHESES; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.AUTO_PAIR_QUOTATIONS; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.AUTO_PAIR_SQUARE_BRACKETS; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.ENABLE_AUTO_SAVE; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.EXPAND_TAB; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.SHOW_ANNOTATION_RULER; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.SHOW_CONTENT_ASSIST_AUTOMATICALLY; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.SHOW_FOLDING_RULER; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.SHOW_LINE_NUMBER_RULER; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.SHOW_OCCURRENCES; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.SHOW_OVERVIEW_RULER; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.SHOW_WHITESPACES; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.SHOW_ZOOM_RULER; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.SMART_INDENTATION; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.SOFT_WRAP; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.TAB_SIZE; + +/** + * The class contains methods to simplify the work with editor preferences. + * + * @author Roman Nikitenko + */ +@Singleton +public class EditorPreferencesManager { + + /** The editor settings property name. */ + private static final String EDITOR_PREFERENCES_PROPERTY = "editorSettings"; + + private final static Map names = new HashMap<>(); + private static Map defaultPreferences; + + private final PreferencesManager preferencesManager; + + @Inject + public EditorPreferencesManager(EditorLocalizationConstants locale, + PreferencesManager preferencesManager) { + this.preferencesManager = preferencesManager; + + names.put(TAB_SIZE.toString(), locale.propertyTabSize()); + names.put(EXPAND_TAB.toString(), locale.propertyExpandTab()); + names.put(AUTO_PAIR_PARENTHESES.toString(), locale.propertyAutoPairParentheses()); + names.put(AUTO_PAIR_BRACES.toString(), locale.propertyAutoPairBraces()); + names.put(AUTO_PAIR_SQUARE_BRACKETS.toString(), locale.propertyAutoPairSquareBrackets()); + names.put(AUTO_PAIR_ANGLE_BRACKETS.toString(), locale.propertyAutoPairAngelBrackets()); + names.put(AUTO_PAIR_QUOTATIONS.toString(), locale.propertyAutoPairQuotations()); + names.put(AUTO_COMPLETE_COMMENTS.toString(), locale.propertyAutoCompleteComments()); + names.put(SMART_INDENTATION.toString(), locale.propertySmartIndentation()); + names.put(SHOW_WHITESPACES.toString(), locale.propertyShowWhitespaces()); + names.put(ENABLE_AUTO_SAVE.toString(), locale.propertyAutoSave()); + names.put(SOFT_WRAP.toString(), locale.propertySoftWrap()); + names.put(SHOW_ANNOTATION_RULER.toString(), locale.propertyShowAnnotationRuler()); + names.put(SHOW_LINE_NUMBER_RULER.toString(), locale.propertyShowLineNumberRuler()); + names.put(SHOW_FOLDING_RULER.toString(), locale.propertyShowFoldingRuler()); + names.put(SHOW_OVERVIEW_RULER.toString(), locale.propertyShowOverviewRuler()); + names.put(SHOW_ZOOM_RULER.toString(), locale.propertyShowZoomRuler()); + names.put(SHOW_OCCURRENCES.toString(), locale.propertyShowOccurrences()); + names.put(SHOW_CONTENT_ASSIST_AUTOMATICALLY.toString(), locale.propertyShowContentAssistAutomatically()); + + getDefaultEditorPreferences(); + } + + /** Returns default editor preferences */ + public static Map getDefaultEditorPreferences() { + if (defaultPreferences != null) { + return defaultPreferences; + } + defaultPreferences = new HashMap<>(); + + // TextViewOptions (tabs) + defaultPreferences.put(TAB_SIZE.toString(), new JSONNumber(4)); + defaultPreferences.put(EXPAND_TAB.toString(), JSONBoolean.getInstance(true)); + + // Edit + defaultPreferences.put(ENABLE_AUTO_SAVE.toString(), JSONBoolean.getInstance(true)); + defaultPreferences.put(SOFT_WRAP.toString(), JSONBoolean.getInstance(false)); + + // SourceCodeActions (typing) + defaultPreferences.put(AUTO_PAIR_PARENTHESES.toString(), JSONBoolean.getInstance(true)); + defaultPreferences.put(AUTO_PAIR_BRACES.toString(), JSONBoolean.getInstance(true)); + defaultPreferences.put(AUTO_PAIR_SQUARE_BRACKETS.toString(), JSONBoolean.getInstance(true)); + defaultPreferences.put(AUTO_PAIR_ANGLE_BRACKETS.toString(), JSONBoolean.getInstance(true)); + defaultPreferences.put(AUTO_PAIR_QUOTATIONS.toString(), JSONBoolean.getInstance(true)); + defaultPreferences.put(AUTO_COMPLETE_COMMENTS.toString(), JSONBoolean.getInstance(true)); + defaultPreferences.put(SMART_INDENTATION.toString(), JSONBoolean.getInstance(true)); + + //white spaces + defaultPreferences.put(SHOW_WHITESPACES.toString(), JSONBoolean.getInstance(false)); + + // editor features (rulers) + defaultPreferences.put(SHOW_ANNOTATION_RULER.toString(), JSONBoolean.getInstance(true)); + defaultPreferences.put(SHOW_LINE_NUMBER_RULER.toString(), JSONBoolean.getInstance(true)); + defaultPreferences.put(SHOW_FOLDING_RULER.toString(), JSONBoolean.getInstance(true)); + defaultPreferences.put(SHOW_OVERVIEW_RULER.toString(), JSONBoolean.getInstance(true)); + defaultPreferences.put(SHOW_ZOOM_RULER.toString(), JSONBoolean.getInstance(true)); + + // language tools + defaultPreferences.put(SHOW_OCCURRENCES.toString(), JSONBoolean.getInstance(true)); + defaultPreferences.put(SHOW_CONTENT_ASSIST_AUTOMATICALLY.toString(), JSONBoolean.getInstance(true)); + + return defaultPreferences; + } + + /** + * Returns property name using special id. + * Note: method can return {@code null} if name not found. + * + * @param propertyId + * id for which name will be returned + * @return name of the property + */ + @Nullable + public String getPropertyNameById(@NotNull String propertyId) { + return names.get(propertyId); + } + + public void storeEditorPreferences(Map editorPreferences) { + JSONObject jsonPreferences = new JSONObject(); + + editorPreferences.keySet().forEach(property -> jsonPreferences.put(property, editorPreferences.get(property))); + + preferencesManager.setValue(EDITOR_PREFERENCES_PROPERTY, jsonPreferences.toString()); + } + + /** Returns saved preferences for editor if they exist or default preferences otherwise. */ + public Map getEditorPreferences() { + String jsonPreferences = preferencesManager.getValue(EDITOR_PREFERENCES_PROPERTY); + if (jsonPreferences == null) { + return defaultPreferences; + } + + Map savedPreferences = readPreferencesFromJson(jsonPreferences); + defaultPreferences.keySet().stream() + .filter(property -> !savedPreferences.containsKey(property)) + .forEach(property -> savedPreferences.put(property, defaultPreferences.get(property))); + return savedPreferences; + } + + /** Returns saved editor preferences if they exist or default preferences otherwise for given set properties. */ + public Map getEditorPreferencesFor(EnumSet filter) { + Map editorPreferences = getEditorPreferences(); + Map result = new HashMap<>(filter.size()); + + for (EditorProperties property : filter) { + String key = property.toString(); + if (editorPreferences.containsKey(key)) { + result.put(key, editorPreferences.get(key)); + } + } + return result; + } + + /** Returns all saved preferences for editor in json format if they exist or default preferences otherwise. */ + public JSONObject getJsonEditorPreferences() { + JSONObject jsonPreferences = new JSONObject(); + Map editorPreferences = getEditorPreferences(); + + editorPreferences.keySet() + .forEach(property -> jsonPreferences.put(property, editorPreferences.get(property))); + + return jsonPreferences; + } + + /** Returns saved editor preferences in json format if they exist or default preferences otherwise for given set properties. */ + public JSONObject getJsonEditorPreferencesFor(EnumSet filter) { + JSONObject jsonPreferences = new JSONObject(); + Map editorPreferences = getEditorPreferences(); + + for (EditorProperties property : filter) { + String key = property.toString(); + if (editorPreferences.containsKey(key)) { + jsonPreferences.put(key, editorPreferences.get(key)); + } + } + return jsonPreferences; + } + + public JSONValue getJsonValueFor(EditorProperties property) { + return property != null ? getEditorPreferences().get(property.toString()) : null; + } + + public Integer getNumberValueFor(EditorProperties property) { + JSONValue jsonValue = getJsonValueFor(property); + if (jsonValue == null) { + return null; + } + + JSONNumber jsonNumber = jsonValue.isNumber(); + if (jsonNumber == null) { + return null; + } + + Double result = jsonNumber.doubleValue(); + return result.intValue(); + } + + public Boolean getBooleanValueFor(EditorProperties property) { + JSONValue jsonValue = getJsonValueFor(property); + if (jsonValue == null) { + return null; + } + + JSONBoolean jsonBoolean = jsonValue.isBoolean(); + if (jsonBoolean == null) { + return null; + } + return jsonBoolean.booleanValue(); + } + + private static Map readPreferencesFromJson(String jsonPreferences) { + Map result = new HashMap<>(); + JSONValue parsed = JSONParser.parseStrict(jsonPreferences); + + JSONObject jsonObj = parsed.isObject(); + if (jsonObj != null) { + jsonObj.keySet().forEach(key -> result.put(key, jsonObj.get(key))); + } + return result; + } +} diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/EditorPreferencesModule.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/EditorPreferencesModule.java index 40325cb57f4..f5d65f69895 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/EditorPreferencesModule.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/EditorPreferencesModule.java @@ -15,6 +15,7 @@ import com.google.gwt.inject.client.multibindings.GinMultibinder; import org.eclipse.che.ide.api.preferences.PreferencePagePresenter; +import org.eclipse.che.ide.editor.preferences.editorproperties.EditorPropertiesPresenter; import org.eclipse.che.ide.editor.preferences.editorproperties.propertiessection.EditorPropertiesSectionPresenter; import org.eclipse.che.ide.editor.preferences.editorproperties.sections.EditPropertiesSection; import org.eclipse.che.ide.editor.preferences.editorproperties.sections.EditorPreferenceSectionFactory; @@ -47,6 +48,11 @@ protected void configure() { .implement(EditorPreferenceSection.class, EditorPropertiesSectionPresenter.class) .build(EditorPreferenceSectionFactory.class)); + GinMultibinder editorPreferenceSectionsBinder = + GinMultibinder.newSetBinder(binder(), EditorPreferenceSection.class); + editorPreferenceSectionsBinder.addBinding().to(KeyMapsPreferencePresenter.class); + editorPreferenceSectionsBinder.addBinding().to(EditorPropertiesPresenter.class); + GinMultibinder editorPropertiesSectionBinder = GinMultibinder.newSetBinder(binder(), EditorPropertiesSection.class); diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/editorproperties/EditorProperties.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/editorproperties/EditorProperties.java index 87f620b62a3..1796a57dc9e 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/editorproperties/EditorProperties.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/editorproperties/EditorProperties.java @@ -35,7 +35,8 @@ public enum EditorProperties { //White spaces section SHOW_WHITESPACES("showWhitespaces"), - //Soft wrap + //Edit section + ENABLE_AUTO_SAVE("enableAutoSave"), SOFT_WRAP("wordWrap"), //Rulers section diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/editorproperties/EditorPropertiesManager.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/editorproperties/EditorPropertiesManager.java index a5df9caee8e..400a86cb30d 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/editorproperties/EditorPropertiesManager.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/editorproperties/EditorPropertiesManager.java @@ -48,9 +48,12 @@ /** * The class contains methods to simplify the work with editor properties. * + * @deprecated in favor of {@link org.eclipse.che.ide.editor.preferences.EditorPreferencesManager} + * * @author Roman Nikitenko */ @Singleton +@Deprecated public class EditorPropertiesManager { /** The editor settings property name. */ diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/editorproperties/propertiessection/EditorPropertiesSectionPresenter.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/editorproperties/propertiessection/EditorPropertiesSectionPresenter.java index 9c097accd26..c3658e7f64d 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/editorproperties/propertiessection/EditorPropertiesSectionPresenter.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/editorproperties/propertiessection/EditorPropertiesSectionPresenter.java @@ -14,11 +14,9 @@ import com.google.gwt.user.client.ui.AcceptsOneWidget; import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.AssistedInject; -import com.google.web.bindery.event.shared.EventBus; -import org.eclipse.che.ide.api.event.EditorSettingsChangedEvent; import org.eclipse.che.ide.editor.preferences.EditorPreferenceSection; -import org.eclipse.che.ide.editor.preferences.editorproperties.EditorPropertiesManager; +import org.eclipse.che.ide.editor.preferences.EditorPreferencesManager; import java.util.List; import java.util.Map; @@ -31,35 +29,34 @@ public class EditorPropertiesSectionPresenter implements EditorPreferenceSection, EditorPropertiesSectionView.ActionDelegate { /** The preference page presenter. */ private EditorPreferenceSection.ParentPresenter parentPresenter; - private final EventBus eventBus; private final EditorPropertiesSectionView view; - private final EditorPropertiesManager editorPropertiesManager; + private final EditorPreferencesManager editorPreferencesManager; private final List properties; @AssistedInject public EditorPropertiesSectionPresenter(@Assisted String title, @Assisted List properties, final EditorPropertiesSectionView view, - final EventBus eventBus, - final EditorPropertiesManager editorPropertiesManager) { + final EditorPreferencesManager editorPreferencesManager) { this.view = view; this.view.setSectionTitle(title); this.view.setDelegate(this); this.properties = properties; - this.eventBus = eventBus; - this.editorPropertiesManager = editorPropertiesManager; + this.editorPreferencesManager = editorPreferencesManager; } @Override public void storeChanges() { - Map editorProperties = editorPropertiesManager.getEditorProperties(); - for (String property : editorProperties.keySet()) { - JSONValue actualValue = view.getPropertyValueById(property); - actualValue = actualValue != null ? actualValue : editorProperties.get(property); - editorProperties.put(property, actualValue); - } - editorPropertiesManager.storeEditorProperties(editorProperties); - eventBus.fireEvent(new EditorSettingsChangedEvent()); + Map editorPreferences = editorPreferencesManager.getEditorPreferences(); + editorPreferences.keySet() + .forEach(property -> { + JSONValue actualValue = view.getPropertyValueById(property); + if (actualValue != null) { + editorPreferences.put(property, actualValue); + } + }); + + editorPreferencesManager.storeEditorPreferences(editorPreferences); } @Override @@ -69,14 +66,13 @@ public void refresh() { @Override public boolean isDirty() { - Map editorProperties = editorPropertiesManager.getEditorProperties(); - for (String property : editorProperties.keySet()) { - JSONValue actualValue = view.getPropertyValueById(property); - if (actualValue != null && !actualValue.equals(editorProperties.get(property))) { - return true; - } - } - return false; + Map editorPreferences = editorPreferencesManager.getEditorPreferences(); + return editorPreferences.keySet() + .stream() + .anyMatch(property -> { + JSONValue actualValue = view.getPropertyValueById(property); + return actualValue != null && !actualValue.equals(editorPreferences.get(property)); + }); } @Override @@ -90,16 +86,17 @@ public void setParent(final ParentPresenter parent) { this.parentPresenter = parent; } - private void addProperties() { - Map editorProperties = editorPropertiesManager.getEditorProperties(); - for (String property : properties) { - JSONValue value = editorProperties.get(property); - view.addProperty(property, value); - } - } - @Override public void onPropertyChanged() { parentPresenter.signalDirtyState(); } + + private void addProperties() { + Map editorPreferences = editorPreferencesManager.getEditorPreferences(); + + properties.forEach(property -> { + JSONValue value = editorPreferences.get(property); + view.addProperty(property, value); + }); + } } diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/editorproperties/propertiessection/EditorPropertiesSectionViewImpl.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/editorproperties/propertiessection/EditorPropertiesSectionViewImpl.java index 0063f4ca14b..f975ac68a8f 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/editorproperties/propertiessection/EditorPropertiesSectionViewImpl.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/editorproperties/propertiessection/EditorPropertiesSectionViewImpl.java @@ -21,7 +21,7 @@ import com.google.inject.Inject; import org.eclipse.che.commons.annotation.Nullable; -import org.eclipse.che.ide.editor.preferences.editorproperties.EditorPropertiesManager; +import org.eclipse.che.ide.editor.preferences.EditorPreferencesManager; import org.eclipse.che.ide.editor.preferences.editorproperties.property.EditorPropertyWidget; import org.eclipse.che.ide.editor.preferences.editorproperties.property.EditorPropertyWidgetFactory; @@ -44,15 +44,15 @@ public class EditorPropertiesSectionViewImpl extends Composite implements Editor Label sectionTitle; private final EditorPropertyWidgetFactory editorPropertyWidgetFactory; - private final EditorPropertiesManager editorPropertiesManager; + private final EditorPreferencesManager editorPreferencesManager; private ActionDelegate delegate; private Map properties = new HashMap<>(); @Inject public EditorPropertiesSectionViewImpl(EditorPropertyWidgetFactory editorPropertyWidgetFactory, - EditorPropertiesManager editorPropertiesManager) { + EditorPreferencesManager editorPreferencesManager) { this.editorPropertyWidgetFactory = editorPropertyWidgetFactory; - this.editorPropertiesManager = editorPropertiesManager; + this.editorPreferencesManager = editorPreferencesManager; initWidget(UI_BINDER.createAndBindUi(this)); } @@ -91,7 +91,7 @@ public void addProperty(@NotNull String propertyId, JSONValue value) { return; } - String propertyName = editorPropertiesManager.getPropertyNameById(propertyId); + String propertyName = editorPreferencesManager.getPropertyNameById(propertyId); if (propertyName == null) { return; } diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/editorproperties/sections/EditPropertiesSection.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/editorproperties/sections/EditPropertiesSection.java index a5b39746162..a70ec9a5cab 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/editorproperties/sections/EditPropertiesSection.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/editorproperties/sections/EditPropertiesSection.java @@ -13,6 +13,8 @@ import com.google.inject.Inject; import com.google.inject.Singleton; import org.eclipse.che.ide.api.editor.EditorLocalizationConstants; + +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.ENABLE_AUTO_SAVE; import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.SOFT_WRAP; import java.util.Arrays; @@ -29,7 +31,7 @@ public class EditPropertiesSection implements EditorPropertiesSection { @Inject public EditPropertiesSection(EditorLocalizationConstants locale) { this.locale = locale; - properties = Arrays.asList(SOFT_WRAP.toString()); + properties = Arrays.asList(ENABLE_AUTO_SAVE.toString(), SOFT_WRAP.toString()); } @Override diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/keymaps/KeymapsPreferenceViewImpl.ui.xml b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/keymaps/KeymapsPreferenceViewImpl.ui.xml index 6a4f7249516..04573e982e6 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/keymaps/KeymapsPreferenceViewImpl.ui.xml +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/preferences/keymaps/KeymapsPreferenceViewImpl.ui.xml @@ -25,6 +25,10 @@ line-height: 25px; } + .mainSection { + margin-bottom: 40px; + } + .titleSection { background-color: editorPreferenceCategoryBackgroundColor; line-height: 25px; @@ -40,7 +44,7 @@ } - + diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/synchronization/EditorGroupSynchronizationImpl.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/synchronization/EditorGroupSynchronizationImpl.java index c54025a229a..252966dafff 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/synchronization/EditorGroupSynchronizationImpl.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/synchronization/EditorGroupSynchronizationImpl.java @@ -60,10 +60,30 @@ public class EditorGroupSynchronizationImpl implements EditorGroupSynchronizatio @Override public void addEditor(EditorPartPresenter editor) { DocumentHandle documentHandle = getDocumentHandleFor(editor); - if (documentHandle != null) { + if (documentHandle == null) { + return; + } + + if (synchronizedEditors.isEmpty()) { HandlerRegistration handlerRegistration = documentHandle.getDocEventBus().addHandler(DocumentChangeEvent.TYPE, this); synchronizedEditors.put(editor, handlerRegistration); + return; + } + + EditorPartPresenter groupMember = synchronizedEditors.keySet().iterator().next(); + if ((groupMember instanceof EditorWithAutoSave) && !((EditorWithAutoSave)groupMember).isAutoSaveEnabled()) { + //group can contains unsaved content - we need update content for the editor + Document editorDocument = documentHandle.getDocument(); + Document groupMemberDocument = getDocumentHandleFor(groupMember).getDocument(); + + String oldContent = editorDocument.getContents(); + String groupMemberContent = groupMemberDocument.getContents(); + + editorDocument.replace(0, oldContent.length(), groupMemberContent); } + + HandlerRegistration handlerRegistration = documentHandle.getDocEventBus().addHandler(DocumentChangeEvent.TYPE, this); + synchronizedEditors.put(editor, handlerRegistration); } @Override @@ -74,10 +94,6 @@ public void onActiveEditorChanged(@NotNull EditorPartPresenter activeEditor) { @Override public void removeEditor(EditorPartPresenter editor) { - if (editor.isDirty()) { - editor.doSave(); - } - HandlerRegistration handlerRegistration = synchronizedEditors.remove(editor); if (handlerRegistration != null) { handlerRegistration.removeHandler(); @@ -90,9 +106,7 @@ public void removeEditor(EditorPartPresenter editor) { @Override public void unInstall() { - for (HandlerRegistration handlerRegistration : synchronizedEditors.values()) { - handlerRegistration.removeHandler(); - } + synchronizedEditors.values().forEach(HandlerRegistration::removeHandler); if (fileContentUpdateHandlerRegistration != null) { fileContentUpdateHandlerRegistration.removeHandler(); @@ -164,7 +178,7 @@ private void updateContent(String newContent, String oldStamp, VirtualFile virtu final String oldContent = document.getContents(); final TextPosition cursorPosition = document.getCursorPosition(); - if (!(virtualFile instanceof File)){ + if (!(virtualFile instanceof File)) { replaceContent(document, newContent, oldContent, cursorPosition); return; } @@ -198,9 +212,7 @@ private DocumentHandle getDocumentHandleFor(EditorPartPresenter editor) { } private void resolveAutoSave() { - for (EditorPartPresenter editor : synchronizedEditors.keySet()) { - resolveAutoSaveFor(editor); - } + synchronizedEditors.keySet().forEach(this::resolveAutoSaveFor); } private void resolveAutoSaveFor(EditorPartPresenter editor) { @@ -214,8 +226,6 @@ private void resolveAutoSaveFor(EditorPartPresenter editor) { return; } - if (editorWithAutoSave.isAutoSaveEnabled()) { - editorWithAutoSave.disableAutoSave(); - } + editorWithAutoSave.disableAutoSave(); } } diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/synchronization/workingCopy/EditorWorkingCopySynchronizer.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/synchronization/workingCopy/EditorWorkingCopySynchronizer.java new file mode 100644 index 00000000000..42474d63602 --- /dev/null +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/synchronization/workingCopy/EditorWorkingCopySynchronizer.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * 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.workingCopy; + +import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcPromise; +import org.eclipse.che.ide.api.editor.reconciler.DirtyRegion; + +/** + * The synchronizer of content for opened files with working copies on server side. + * + * @author Roman Nikitenko + */ +public interface EditorWorkingCopySynchronizer { + /** + * Sends the text change of editor content to sync its working copy on server side. + * + * @param filePath + * path to the file which content is needed to sync + * @param projectPath + * the path to the project which contains the file to sync + * @param dirtyRegion + * describes a document range which has been changed + */ + JsonRpcPromise synchronize(String filePath, String projectPath, DirtyRegion dirtyRegion); +} diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/synchronization/workingCopy/EditorWorkingCopySynchronizerImpl.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/synchronization/workingCopy/EditorWorkingCopySynchronizerImpl.java new file mode 100644 index 00000000000..c2d9877ec5f --- /dev/null +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/editor/synchronization/workingCopy/EditorWorkingCopySynchronizerImpl.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * 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.workingCopy; + +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcPromise; +import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerConfigurator; +import org.eclipse.che.api.core.jsonrpc.commons.RequestTransmitter; +import org.eclipse.che.api.project.shared.dto.EditorChangesDto; +import org.eclipse.che.api.project.shared.dto.EditorChangesDto.Type; +import org.eclipse.che.api.project.shared.dto.ServerError; +import org.eclipse.che.ide.api.editor.reconciler.DirtyRegion; +import org.eclipse.che.ide.dto.DtoFactory; +import org.eclipse.che.ide.util.loging.Log; + +import static org.eclipse.che.api.project.shared.dto.EditorChangesDto.Type.INSERT; +import static org.eclipse.che.api.project.shared.dto.EditorChangesDto.Type.REMOVE; + +/** + * Default implementation of {@link EditorWorkingCopySynchronizer} which provides synchronization working copy on server side. + * + * @author Roman Nikitenko + */ +@Singleton +public class EditorWorkingCopySynchronizerImpl implements EditorWorkingCopySynchronizer { + private static final String ENDPOINT_ID = "ws-agent"; + private static final String WORKING_COPY_ERROR_METHOD = "track:editor-working-copy-error"; + private static final String EDITOR_CONTENT_CHANGES_METHOD = "track:editor-content-changes"; + + private DtoFactory dtoFactory; + private RequestTransmitter requestTransmitter; + + @Inject + public EditorWorkingCopySynchronizerImpl(DtoFactory dtoFactory, + RequestTransmitter requestTransmitter) { + this.dtoFactory = dtoFactory; + this.requestTransmitter = requestTransmitter; + } + + @Inject + public void configureHandler(RequestHandlerConfigurator configurator) { + configurator.newConfiguration() + .methodName(WORKING_COPY_ERROR_METHOD) + .paramsAsDto(ServerError.class) + .noResult() + .withConsumer(this::onError); + } + + public JsonRpcPromise synchronize(String filePath, String projectPath, DirtyRegion dirtyRegion) { + Type type = dirtyRegion.getType().equals(DirtyRegion.INSERT) ? INSERT : REMOVE; + EditorChangesDto changes = dtoFactory.createDto(EditorChangesDto.class) + .withType(type) + .withProjectPath(projectPath) + .withFileLocation(filePath) + .withOffset(dirtyRegion.getOffset()) + .withText(dirtyRegion.getText()); + + int length = dirtyRegion.getLength(); + if (DirtyRegion.REMOVE.equals(dirtyRegion.getType())) { + changes.withRemovedCharCount(length); + } else { + changes.withLength(length); + } + + return requestTransmitter.newRequest() + .endpointId(ENDPOINT_ID) + .methodName(EDITOR_CONTENT_CHANGES_METHOD) + .paramsAsDto(changes) + .sendAndReceiveResultAsEmpty(); + } + + private void onError(ServerError error) { + Log.error(getClass(), error.getMessage()); + } +} diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/part/editor/EditorPartStackPresenter.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/part/editor/EditorPartStackPresenter.java index 758e02fe815..3b9db9156ee 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/part/editor/EditorPartStackPresenter.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/part/editor/EditorPartStackPresenter.java @@ -214,8 +214,18 @@ public void addPart(@NotNull PartPresenter part) { editorPart.addPropertyListener(new PropertyListener() { @Override public void propertyChanged(PartPresenter source, int propId) { - if (propId == EditorPartPresenter.PROP_INPUT && source instanceof EditorPartPresenter) { - editorTab.setReadOnlyMark(((EditorPartPresenter)source).getEditorInput().getFile().isReadOnly()); + if (!(source instanceof EditorPartPresenter)) { + return; + } + + boolean isReadOnly = ((EditorPartPresenter)source).getEditorInput().getFile().isReadOnly(); + if (propId == EditorPartPresenter.PROP_INPUT) { + editorTab.setReadOnlyMark(isReadOnly); + return; + } + + if (!isReadOnly && propId == EditorPartPresenter.PROP_DIRTY) { + editorTab.setUnsavedDataMark(((EditorPartPresenter)source).isDirty()); } } }); diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/part/widgets/editortab/EditorTabWidget.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/part/widgets/editortab/EditorTabWidget.java index 72348e82a0d..aaf87d2f448 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/part/widgets/editortab/EditorTabWidget.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/part/widgets/editortab/EditorTabWidget.java @@ -271,6 +271,15 @@ public EditorPartPresenter getRelativeEditorPart() { return relatedEditorPart; } + @Override + public void setUnsavedDataMark(boolean hasUnsavedData) { + if (hasUnsavedData) { + getElement().setAttribute("unsaved", ""); + } else { + getElement().removeAttribute("unsaved"); + } + } + /** {@inheritDoc} */ @Override public void setPinMark(boolean pinned) { diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/part/widgets/editortab/EditorTabWidget.ui.xml b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/part/widgets/editortab/EditorTabWidget.ui.xml index fb35e09d9ce..4d1647468be 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/part/widgets/editortab/EditorTabWidget.ui.xml +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/part/widgets/editortab/EditorTabWidget.ui.xml @@ -31,6 +31,7 @@ @eval activeEditorReadonlyTabBackgroundColor org.eclipse.che.ide.api.theme.Style.theme.activeEditorReadonlyTabBackgroundColor(); @eval editorTabPinBackgroundColor org.eclipse.che.ide.api.theme.Style.theme.editorTabPinBackgroundColor(); @eval editorTabPinDropShadow org.eclipse.che.ide.api.theme.Style.theme.editorTabPinDropShadow(); + @eval unsavedMarkColor org.eclipse.che.ide.api.theme.Style.getBadgeBackgroundColor(); .mainPanel { -webkit-box-sizing: border-box; @@ -63,6 +64,14 @@ overflow: hidden; } + .mainPanel[unsaved] .iconPanel::after { + color: unsavedMarkColor; + content: '*'; + float: inherit; + margin-top: 9px; + margin-left: 19px; + } + .iconPanel svg { transform-origin: 0px 0px 0px; position: absolute; diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/project/ResolvingProjectStateHolderRegistry.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/project/ResolvingProjectStateHolderRegistry.java index dd7e2f06be1..ce5d876c4a7 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/project/ResolvingProjectStateHolderRegistry.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/project/ResolvingProjectStateHolderRegistry.java @@ -13,7 +13,7 @@ import org.eclipse.che.commons.annotation.Nullable; /** - * Registry for implementations of {@link org.eclipse.che.ide.project.ResolvingProjectStateHolder}. + * Registry for implementations of {@link ResolvingProjectStateHolder}. * * @author Roman Nikitenko */ diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/perspectives/general/AbstractPerspective.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/perspectives/general/AbstractPerspective.java index 34a5ff28a16..e01efda914f 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/perspectives/general/AbstractPerspective.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/perspectives/general/AbstractPerspective.java @@ -286,7 +286,6 @@ public void addPart(@NotNull PartPresenter part, @NotNull PartStackType type, @N List rules = part.getRules(); if (rules.isEmpty() || rules.contains(perspectiveId)) { destPartStack.addPart(part, constraint); - return; } } diff --git a/ide/che-core-ide-app/src/test/java/org/eclipse/che/ide/editor/synchronization/EditorGroupSynchronizationImplTest.java b/ide/che-core-ide-app/src/test/java/org/eclipse/che/ide/editor/synchronization/EditorGroupSynchronizationImplTest.java index 4683dbee382..eb7ecf4cc5a 100644 --- a/ide/che-core-ide-app/src/test/java/org/eclipse/che/ide/editor/synchronization/EditorGroupSynchronizationImplTest.java +++ b/ide/che-core-ide-app/src/test/java/org/eclipse/che/ide/editor/synchronization/EditorGroupSynchronizationImplTest.java @@ -89,40 +89,51 @@ public void init() { when(((TextEditor)openedEditor1).getDocument()).thenReturn(document); when(((TextEditor)openedEditor2).getDocument()).thenReturn(document); + when(document.getContents()).thenReturn("some content"); + when(documentEventBus.addHandler((Event.Type)anyObject(), anyObject())).thenReturn(handlerRegistration); editorGroupSynchronization = new EditorGroupSynchronizationImpl(eventBus, documentStorage, notificationManager); + } + + @Test + public void shouldAddEditor() { + reset(documentEventBus); editorGroupSynchronization.addEditor(activeEditor); - editorGroupSynchronization.addEditor(openedEditor1); - editorGroupSynchronization.addEditor(openedEditor2); - editorGroupSynchronization.onActiveEditorChanged(activeEditor); + + verify(documentEventBus).addHandler(Matchers.anyObject(), eq(editorGroupSynchronization)); } @Test - public void shouldAddEditor() { + public void shouldUpdateContentAtAddingEditorWhenGroupHasUnsavedData() { + editorGroupSynchronization.addEditor(openedEditor1); reset(documentEventBus); + when(((TextEditor)openedEditor1).getDocument()).thenReturn(document); + when(((EditorWithAutoSave)openedEditor1).isAutoSaveEnabled()).thenReturn(false); editorGroupSynchronization.addEditor(activeEditor); + editorGroupSynchronization.onActiveEditorChanged(activeEditor); + verify((EditorWithAutoSave)openedEditor1).isAutoSaveEnabled(); + verify(document, times(2)).getContents(); + verify(document).replace(anyInt(), anyInt(), anyString()); verify(documentEventBus).addHandler(Matchers.anyObject(), eq(editorGroupSynchronization)); } @Test public void shouldRemoveEditorFromGroup() { - when(activeEditor.isDirty()).thenReturn(true); - editorGroupSynchronization.addEditor(activeEditor); editorGroupSynchronization.removeEditor(activeEditor); - //should save content before closing (autosave will not have time to do it) - verify(activeEditor).doSave(); verify(handlerRegistration).removeHandler(); } @Test public void shouldRemoveAllEditorsFromGroup() { + addEditorsToGroup(); + editorGroupSynchronization.unInstall(); verify(handlerRegistration, times(3)).removeHandler(); @@ -151,6 +162,7 @@ public void shouldApplyChangesFromActiveEditor() { when(documentChangeEvent.getDocument()).thenReturn(documentHandle1); when(documentHandle1.isSameAs(documentHandle)).thenReturn(true); + addEditorsToGroup(); editorGroupSynchronization.onDocumentChange(documentChangeEvent); verify(document, times(2)).replace(eq(offset), eq(removeCharCount), eq(text)); @@ -158,10 +170,20 @@ public void shouldApplyChangesFromActiveEditor() { @Test public void shouldResolveAutoSave() { + addEditorsToGroup(); + // AutoSave for active editor should always be enabled, // but AutoSave for other editors with the same path should be disabled verify(((EditorWithAutoSave)activeEditor)).enableAutoSave(); verify(((EditorWithAutoSave)openedEditor1)).disableAutoSave(); verify(((EditorWithAutoSave)openedEditor2)).disableAutoSave(); } + + private void addEditorsToGroup() { + editorGroupSynchronization.addEditor(openedEditor1); + editorGroupSynchronization.addEditor(openedEditor2); + editorGroupSynchronization.addEditor(activeEditor); + + editorGroupSynchronization.onActiveEditorChanged(activeEditor); + } } diff --git a/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionEditorBuilder.java b/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionEditorBuilder.java index 22f6085ef82..8ded313ca7e 100644 --- a/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionEditorBuilder.java +++ b/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionEditorBuilder.java @@ -14,7 +14,7 @@ import com.google.inject.Provider; import org.eclipse.che.ide.api.editor.defaulteditor.EditorBuilder; -import org.eclipse.che.ide.api.editor.editorconfig.AutoSaveTextEditorConfiguration; +import org.eclipse.che.ide.api.editor.editorconfig.DefaultTextEditorConfiguration; import org.eclipse.che.ide.api.editor.texteditor.TextEditor; /** @@ -35,7 +35,7 @@ public OrionEditorBuilder(Provider orionTextEditorProvider @Override public TextEditor buildEditor() { final OrionEditorPresenter editor = orionTextEditorProvider.get(); - editor.initialize(new AutoSaveTextEditorConfiguration()); + editor.initialize(new DefaultTextEditorConfiguration()); return editor; } } diff --git a/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionEditorInit.java b/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionEditorInit.java index aff5f60cb03..16443e08283 100644 --- a/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionEditorInit.java +++ b/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionEditorInit.java @@ -15,6 +15,7 @@ import org.eclipse.che.ide.api.editor.annotation.AnnotationModel; import org.eclipse.che.ide.api.editor.annotation.HasAnnotationRendering; import org.eclipse.che.ide.api.editor.annotation.QueryAnnotationsEvent; +import org.eclipse.che.ide.api.editor.autosave.AutoSaveMode; import org.eclipse.che.ide.api.editor.changeintercept.ChangeInterceptorProvider; import org.eclipse.che.ide.api.editor.changeintercept.TextChange; import org.eclipse.che.ide.api.editor.changeintercept.TextChangeInterceptor; @@ -63,6 +64,7 @@ public class OrionEditorInit { private static final String CONTENT_ASSIST = "Content assist"; private static final String QUICK_FIX = "Quick fix"; + private final AutoSaveMode autoSaveMode; private final TextEditorConfiguration configuration; private final CodeAssistantFactory codeAssistantFactory; private final OrionEditorPresenter textEditor; @@ -71,10 +73,12 @@ public class OrionEditorInit { /** * The quick assist assistant. */ - public OrionEditorInit(final TextEditorConfiguration configuration, + public OrionEditorInit(final AutoSaveMode autoSaveMode, + final TextEditorConfiguration configuration, final CodeAssistantFactory codeAssistantFactory, final QuickAssistAssistant quickAssist, final OrionEditorPresenter textEditor) { + this.autoSaveMode = autoSaveMode; this.configuration = configuration; this.codeAssistantFactory = codeAssistantFactory; this.quickAssist = quickAssist; @@ -96,6 +100,7 @@ public void init(Document document) { configureFormatter(textEditor); configureSignatureHelp(textEditor); addQuickAssistKeyBinding(); + configureAutoSaveMode(documentHandle); } @@ -108,6 +113,12 @@ public void uninstall() { if (signatureHelpProvider != null) { signatureHelpProvider.uninstall(); } + autoSaveMode.uninstall(); + } + + private void configureAutoSaveMode(final DocumentHandle documentHandle) { + autoSaveMode.install(textEditor); + autoSaveMode.setDocumentHandle(documentHandle); } private void configureSignatureHelp(TextEditor textEditor) { @@ -122,7 +133,6 @@ private void configureFormatter(OrionEditorPresenter textEditor) { if (formatter != null) { formatter.install(textEditor); } - } /** diff --git a/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionEditorPresenter.java b/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionEditorPresenter.java index 89f90ffa63d..cc0f798fde4 100644 --- a/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionEditorPresenter.java +++ b/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionEditorPresenter.java @@ -52,6 +52,7 @@ import org.eclipse.che.ide.api.editor.annotation.ClearAnnotationModelEvent; import org.eclipse.che.ide.api.editor.annotation.ClearAnnotationModelHandler; import org.eclipse.che.ide.api.editor.annotation.HasAnnotationRendering; +import org.eclipse.che.ide.api.editor.autosave.AutoSaveMode; import org.eclipse.che.ide.api.editor.codeassist.CodeAssistProcessor; import org.eclipse.che.ide.api.editor.codeassist.CodeAssistantFactory; import org.eclipse.che.ide.api.editor.codeassist.CompletionsSource; @@ -81,8 +82,6 @@ import org.eclipse.che.ide.api.editor.quickfix.QuickAssistAssistant; import org.eclipse.che.ide.api.editor.quickfix.QuickAssistProcessor; import org.eclipse.che.ide.api.editor.quickfix.QuickAssistantFactory; -import org.eclipse.che.ide.api.editor.reconciler.Reconciler; -import org.eclipse.che.ide.api.editor.reconciler.ReconcilerWithAutoSave; import org.eclipse.che.ide.api.editor.signature.SignatureHelp; import org.eclipse.che.ide.api.editor.signature.SignatureHelpProvider; import org.eclipse.che.ide.api.editor.text.LinearRange; @@ -185,6 +184,7 @@ public class OrionEditorPresenter extends AbstractEditorPresenter implements Tex private final AppContext appContext; private final SignatureHelpView signatureHelpView; private final EditorContextMenu contextMenu; + private final AutoSaveMode autoSaveMode; private final ClientServerEventService clientServerEventService; private final AnnotationRendering rendering = new AnnotationRendering(); @@ -226,6 +226,7 @@ public OrionEditorPresenter(final CodeAssistantFactory codeAssistantFactory, final AppContext appContext, final SignatureHelpView signatureHelpView, final EditorContextMenu contextMenu, + final AutoSaveMode autoSaveMode, final ClientServerEventService clientServerEventService) { this.codeAssistantFactory = codeAssistantFactory; this.deletedFilesController = deletedFilesController; @@ -247,6 +248,7 @@ public OrionEditorPresenter(final CodeAssistantFactory codeAssistantFactory, this.appContext = appContext; this.signatureHelpView = signatureHelpView; this.contextMenu = contextMenu; + this.autoSaveMode = autoSaveMode; this.clientServerEventService = clientServerEventService; keyBindingsManager = new TemporaryKeyBindingsManager(); @@ -262,7 +264,8 @@ protected void initializeEditor(final EditorAgent.OpenEditorCallback callback) { quickAssistant.setQuickAssistProcessor(processor); } - editorInit = new OrionEditorInit(configuration, + editorInit = new OrionEditorInit(autoSaveMode, + configuration, this.codeAssistantFactory, this.quickAssistant, this); @@ -474,6 +477,11 @@ public void restoreState() { @Override public void close(boolean save) { + if (resourceChangeHandler != null) { + resourceChangeHandler.removeHandler(); + resourceChangeHandler = null; + } + this.documentStorage.documentClosed(this.document); editorInit.uninstall(); workspaceAgent.removePart(this); @@ -505,7 +513,7 @@ public String getTitleToolTip() { } @Override - public void onClose(@NotNull final AsyncCallback callback) { + public void onClosing(@NotNull final AsyncCallback callback) { if (isDirty()) { dialogFactory.createConfirmDialog( constant.askWindowCloseTitle(), @@ -514,32 +522,20 @@ public void onClose(@NotNull final AsyncCallback callback) { @Override public void accepted() { doSave(); - handleClose(); callback.onSuccess(null); } }, new CancelCallback() { @Override public void cancelled() { - handleClose(); callback.onSuccess(null); } }).show(); } else { - handleClose(); callback.onSuccess(null); } } - @Override - protected void handleClose() { - if (resourceChangeHandler != null) { - resourceChangeHandler.removeHandler(); - resourceChangeHandler = null; - } - super.handleClose(); - } - @Override public TextEditorPartView getView() { return this.editorView; @@ -804,7 +800,7 @@ public void onResize() { public void editorLostFocus() { this.editorView.updateInfoPanelUnfocused(this.document.getLineCount()); this.isFocused = false; - if (isDirty()) { + if (isDirty() && autoSaveMode.isActivated()) { doSave(); } } @@ -905,33 +901,17 @@ public boolean isFocused() { @Override public boolean isAutoSaveEnabled() { - ReconcilerWithAutoSave autoSave = getAutoSave(); - return autoSave != null && autoSave.isAutoSaveEnabled(); - } - - private ReconcilerWithAutoSave getAutoSave() { - Reconciler reconciler = getConfiguration().getReconciler(); - - if (reconciler != null && reconciler instanceof ReconcilerWithAutoSave) { - return ((ReconcilerWithAutoSave)reconciler); - } - return null; + return autoSaveMode.isActivated(); } @Override public void enableAutoSave() { - ReconcilerWithAutoSave autoSave = getAutoSave(); - if (autoSave != null) { - autoSave.enableAutoSave(); - } + autoSaveMode.resume(); } @Override public void disableAutoSave() { - ReconcilerWithAutoSave autoSave = getAutoSave(); - if (autoSave != null) { - autoSave.disableAutoSave(); - } + autoSaveMode.suspend(); } @Override diff --git a/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionEditorWidget.java b/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionEditorWidget.java index af428b99a8b..c0cd42263bb 100644 --- a/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionEditorWidget.java +++ b/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionEditorWidget.java @@ -24,7 +24,6 @@ import com.google.gwt.event.dom.client.FocusHandler; import com.google.gwt.event.dom.client.HasChangeHandlers; import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.json.client.JSONObject; import com.google.gwt.resources.client.CssResource; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; @@ -67,8 +66,6 @@ import org.eclipse.che.ide.api.editor.texteditor.EditorWidget; import org.eclipse.che.ide.api.editor.texteditor.HandlesUndoRedo; import org.eclipse.che.ide.api.editor.texteditor.LineStyler; -import org.eclipse.che.ide.api.event.EditorSettingsChangedEvent; -import org.eclipse.che.ide.api.event.EditorSettingsChangedEvent.EditorSettingsChangedHandler; import org.eclipse.che.ide.api.event.SelectionChangedEvent; import org.eclipse.che.ide.api.event.SelectionChangedHandler; import org.eclipse.che.ide.api.hotkeys.HotKeyItem; @@ -97,7 +94,6 @@ import org.eclipse.che.ide.editor.orion.client.jso.OrionTextViewOverlay; import org.eclipse.che.ide.editor.orion.client.jso.StatusMessageReporterOverlay; import org.eclipse.che.ide.editor.orion.client.jso.UiUtilsOverlay; -import org.eclipse.che.ide.editor.preferences.editorproperties.EditorPropertiesManager; import org.eclipse.che.ide.editor.preferences.keymaps.KeyMapsPreferencePresenter; import org.eclipse.che.ide.status.message.StatusMessageReporter; import org.eclipse.che.ide.util.browser.UserAgent; @@ -136,6 +132,7 @@ public class OrionEditorWidget extends Composite implements EditorWidget, private final ContentAssistWidgetFactory contentAssistWidgetFactory; private final DialogFactory dialogFactory; private final PreferencesManager preferencesManager; + private final OrionSettingsController orionSettingsController; @UiField SimplePanel panel; @@ -152,7 +149,6 @@ public class OrionEditorWidget extends Composite implements EditorWidget, private OrionDocument embeddedDocument; private OrionKeyModeOverlay cheContentAssistMode; - private EditorPropertiesManager editorPropertiesManager; private Keymap keymap; private ContentAssistWidget assistWidget; @@ -172,7 +168,6 @@ public class OrionEditorWidget extends Composite implements EditorWidget, public OrionEditorWidget(final ModuleHolder moduleHolder, final KeyModeInstances keyModeInstances, final EventBus eventBus, - final EditorPropertiesManager editorPropertiesManager, final Provider orionCodeEditWidgetProvider, final ContentAssistWidgetFactory contentAssistWidgetFactory, final DialogFactory dialogFactory, @@ -181,17 +176,17 @@ public OrionEditorWidget(final ModuleHolder moduleHolder, @Assisted final WidgetInitializedCallback widgetInitializedCallback, final Provider editorOptionsProvider, final StatusMessageReporter statusMessageReporter, - final IncrementalFindReportStatusObserver incrementalFindObserver) { + final IncrementalFindReportStatusObserver incrementalFindObserver, + final OrionSettingsController orionSettingsController) { this.contentAssistWidgetFactory = contentAssistWidgetFactory; this.moduleHolder = moduleHolder; this.keyModeInstances = keyModeInstances; this.eventBus = eventBus; this.dialogFactory = dialogFactory; this.preferencesManager = preferencesManager; + this.orionSettingsController = orionSettingsController; initWidget(UIBINDER.createAndBindUi(this)); - this.editorPropertiesManager = editorPropertiesManager; - this.uiUtilsOverlay = moduleHolder.getModule("UiUtils"); // just first choice for the moment @@ -211,13 +206,6 @@ public OrionEditorWidget(final ModuleHolder moduleHolder, statusMessageReporter.registerObserver(incrementalFindObserver); registerPromptFunction(); - eventBus.addHandler(EditorSettingsChangedEvent.TYPE, new EditorSettingsChangedHandler() { - @Override - public void onEditorSettingsChanged(EditorSettingsChangedEvent event) { - final JSONObject properties = editorPropertiesManager.getJsonEditorProperties(); - editorViewOverlay.updateSettings(properties.getJavaScriptObject()); - } - }); } private OrionEditorOptionsOverlay initEditorOptions(OrionEditorOptionsOverlay orionEditorOptionsOverlay, @@ -281,9 +269,7 @@ public boolean isReadOnly() { @Override public void setReadOnly(final boolean isReadOnly) { editorViewOverlay.setReadonly(isReadOnly); - - final JSONObject properties = editorPropertiesManager.getJsonEditorProperties(); - editorViewOverlay.updateSettings(properties.getJavaScriptObject()); + orionSettingsController.updateSettings(); } @Override @@ -797,6 +783,7 @@ private EditorViewCreatedOperation(WidgetInitializedCallback widgetInitializedCa public void apply(OrionEditorViewOverlay arg) throws OperationException { editorViewOverlay = arg; editorOverlay = arg.getEditor(); + orionSettingsController.setEditorViewOverlay(arg); final OrionContentAssistOverlay contentAssist = editorOverlay.getContentAssist(); eventBus.addHandler(SelectionChangedEvent.TYPE, new SelectionChangedHandler() { @@ -830,9 +817,7 @@ public void onKeymapChanged(final KeymapChangeEvent event) { assistWidget = contentAssistWidgetFactory.create(OrionEditorWidget.this, cheContentAssistMode); gutter = initBreakpointRuler(moduleHolder); - final JSONObject editorProperties = editorPropertiesManager.getJsonEditorProperties(); - editorViewOverlay.updateSettings(editorProperties.getJavaScriptObject()); - + orionSettingsController.updateSettings(); widgetInitializedCallback.initialized(OrionEditorWidget.this); } } diff --git a/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionSettingsController.java b/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionSettingsController.java new file mode 100644 index 00000000000..3c1ca163c04 --- /dev/null +++ b/ide/che-core-orion-editor/src/main/java/org/eclipse/che/ide/editor/orion/client/OrionSettingsController.java @@ -0,0 +1,101 @@ +/******************************************************************************* + * 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.orion.client; + +import com.google.gwt.json.client.JSONObject; +import com.google.inject.Inject; +import com.google.web.bindery.event.shared.EventBus; + +import org.eclipse.che.ide.api.event.EditorSettingsChangedEvent; +import org.eclipse.che.ide.api.event.EditorSettingsChangedEvent.EditorSettingsChangedHandler; +import org.eclipse.che.ide.editor.orion.client.jso.OrionEditorViewOverlay; +import org.eclipse.che.ide.editor.preferences.EditorPreferencesManager; +import org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties; + +import java.util.EnumSet; + +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.AUTO_COMPLETE_COMMENTS; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.AUTO_PAIR_ANGLE_BRACKETS; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.AUTO_PAIR_BRACES; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.AUTO_PAIR_PARENTHESES; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.AUTO_PAIR_QUOTATIONS; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.AUTO_PAIR_SQUARE_BRACKETS; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.EXPAND_TAB; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.SHOW_ANNOTATION_RULER; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.SHOW_CONTENT_ASSIST_AUTOMATICALLY; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.SHOW_FOLDING_RULER; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.SHOW_LINE_NUMBER_RULER; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.SHOW_OCCURRENCES; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.SHOW_OVERVIEW_RULER; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.SHOW_WHITESPACES; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.SHOW_ZOOM_RULER; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.SMART_INDENTATION; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.SOFT_WRAP; +import static org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties.TAB_SIZE; + +/** + * The class contains methods to simplify the work with orion settings. + * + * @author Roman Nikitenko + */ +public class OrionSettingsController implements EditorSettingsChangedHandler { + + private OrionEditorViewOverlay editorViewOverlay; + private final EnumSet orionPropertiesSet = EnumSet.noneOf(EditorProperties.class); + + private final EditorPreferencesManager editorPreferencesManager; + + @Inject + public OrionSettingsController(final EventBus eventBus, + final EditorPreferencesManager editorPreferencesManager) { + this.editorPreferencesManager = editorPreferencesManager; + + fillUpEditorPropertiesSet(); + eventBus.addHandler(EditorSettingsChangedEvent.TYPE, this); + } + + public void setEditorViewOverlay(OrionEditorViewOverlay editorViewOverlay) { + this.editorViewOverlay = editorViewOverlay; + } + + public void updateSettings() { + if (editorViewOverlay != null) { + JSONObject properties = editorPreferencesManager.getJsonEditorPreferencesFor(orionPropertiesSet); + editorViewOverlay.updateSettings(properties.getJavaScriptObject()); + } + } + + @Override + public void onEditorSettingsChanged(EditorSettingsChangedEvent event) { + updateSettings(); + } + + private void fillUpEditorPropertiesSet() { + orionPropertiesSet.add(TAB_SIZE); + orionPropertiesSet.add(EXPAND_TAB); + orionPropertiesSet.add(AUTO_PAIR_PARENTHESES); + orionPropertiesSet.add(AUTO_PAIR_BRACES); + orionPropertiesSet.add(AUTO_PAIR_SQUARE_BRACKETS); + orionPropertiesSet.add(AUTO_PAIR_ANGLE_BRACKETS); + orionPropertiesSet.add(AUTO_PAIR_QUOTATIONS); + orionPropertiesSet.add(AUTO_COMPLETE_COMMENTS); + orionPropertiesSet.add(SMART_INDENTATION); + orionPropertiesSet.add(SHOW_WHITESPACES); + orionPropertiesSet.add(SOFT_WRAP); + orionPropertiesSet.add(SHOW_ANNOTATION_RULER); + orionPropertiesSet.add(SHOW_LINE_NUMBER_RULER); + orionPropertiesSet.add(SHOW_FOLDING_RULER); + orionPropertiesSet.add(SHOW_OVERVIEW_RULER); + orionPropertiesSet.add(SHOW_ZOOM_RULER); + orionPropertiesSet.add(SHOW_OCCURRENCES); + orionPropertiesSet.add(SHOW_CONTENT_ASSIST_AUTOMATICALLY); + } +} diff --git a/ide/che-core-orion-editor/src/main/resources/org/eclipse/che/ide/editor/orion/OrionEditor.gwt.xml b/ide/che-core-orion-editor/src/main/resources/org/eclipse/che/ide/editor/orion/OrionEditor.gwt.xml index 0995546a852..f251a5b4983 100644 --- a/ide/che-core-orion-editor/src/main/resources/org/eclipse/che/ide/editor/orion/OrionEditor.gwt.xml +++ b/ide/che-core-orion-editor/src/main/resources/org/eclipse/che/ide/editor/orion/OrionEditor.gwt.xml @@ -15,4 +15,5 @@ + diff --git a/plugins/plugin-java/che-plugin-java-ext-jdt/org-eclipse-jdt-ui/pom.xml b/plugins/plugin-java/che-plugin-java-ext-jdt/org-eclipse-jdt-ui/pom.xml index 8a8c1f4084a..4d07b94ebcb 100644 --- a/plugins/plugin-java/che-plugin-java-ext-jdt/org-eclipse-jdt-ui/pom.xml +++ b/plugins/plugin-java/che-plugin-java-ext-jdt/org-eclipse-jdt-ui/pom.xml @@ -22,6 +22,10 @@ jar Che Plugin :: Java :: Eclipse JDT UI + + com.google.guava + guava + com.google.inject guice @@ -46,6 +50,10 @@ org.eclipse.birt.runtime org.eclipse.equinox.common + + org.eclipse.che.core + che-core-api-core + org.eclipse.che.core che-core-api-dto @@ -54,6 +62,14 @@ org.eclipse.che.core che-core-api-project + + org.eclipse.che.core + che-core-api-project-shared + + + org.eclipse.che.core + che-core-commons-annotations + org.eclipse.che.core che-core-commons-gwt diff --git a/plugins/plugin-java/che-plugin-java-ext-jdt/org-eclipse-jdt-ui/src/main/java/org/eclipse/che/jdt/javaeditor/JavaReconciler.java b/plugins/plugin-java/che-plugin-java-ext-jdt/org-eclipse-jdt-ui/src/main/java/org/eclipse/che/jdt/javaeditor/JavaReconciler.java index a5090fa23fc..e00f5d9acf6 100644 --- a/plugins/plugin-java/che-plugin-java-ext-jdt/org-eclipse-jdt-ui/src/main/java/org/eclipse/che/jdt/javaeditor/JavaReconciler.java +++ b/plugins/plugin-java/che-plugin-java-ext-jdt/org-eclipse-jdt-ui/src/main/java/org/eclipse/che/jdt/javaeditor/JavaReconciler.java @@ -14,13 +14,32 @@ import com.google.inject.Inject; import com.google.inject.Singleton; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.jsonrpc.commons.RequestTransmitter; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.core.notification.EventSubscriber; +import org.eclipse.che.api.project.server.EditorWorkingCopy; +import org.eclipse.che.api.project.server.EditorWorkingCopyManager; +import org.eclipse.che.api.project.server.EditorWorkingCopyUpdatedEvent; +import org.eclipse.che.api.project.server.ProjectManager; +import org.eclipse.che.api.project.server.VirtualFileEntry; +import org.eclipse.che.api.project.shared.dto.EditorChangesDto; +import org.eclipse.che.api.project.shared.dto.ServerError; +import org.eclipse.che.api.project.shared.dto.event.FileTrackingOperationDto; +import org.eclipse.che.api.project.shared.dto.event.FileTrackingOperationDto.Type; +import org.eclipse.che.api.vfs.impl.file.event.detectors.FileTrackingOperationEvent; +import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.dto.server.DtoFactory; import org.eclipse.che.ide.ext.java.shared.dto.HighlightedPosition; import org.eclipse.che.ide.ext.java.shared.dto.Problem; import org.eclipse.che.ide.ext.java.shared.dto.ReconcileResult; import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.Path; import org.eclipse.jdt.core.IBuffer; +import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IProblemRequestor; import org.eclipse.jdt.core.IType; @@ -30,81 +49,293 @@ import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.internal.core.ClassFileWorkingCopy; +import org.eclipse.jdt.internal.core.JavaModel; +import org.eclipse.jdt.internal.core.JavaModelManager; +import org.eclipse.jdt.internal.ui.javaeditor.DocumentAdapter; +import org.eclipse.text.edits.ReplaceEdit; +import org.eclipse.text.edits.TextEdit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.PreDestroy; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Strings.isNullOrEmpty; +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static org.eclipse.che.jdt.javaeditor.JavaReconciler.Mode.ACTIVATED; +import static org.eclipse.che.jdt.javaeditor.JavaReconciler.Mode.DEACTIVATED; +import static org.eclipse.jdt.core.IJavaElement.COMPILATION_UNIT; + /** * @author Evgen Vidolob + * @author Roman Nikitenko */ @Singleton public class JavaReconciler { - private static final Logger LOG = LoggerFactory.getLogger(JavaReconciler.class); + private static final Logger LOG = LoggerFactory.getLogger(JavaReconciler.class); + private static final JavaModel JAVA_MODEL = JavaModelManager.getJavaModelManager().getJavaModel(); + private static final String RECONCILE_ERROR_METHOD = "event:java-reconcile-error"; + private static final String RECONCILE_STATE_CHANGED_METHOD = "event:java-reconcile-state-changed"; + + private final List subscribers = new ArrayList<>(2); + private final EventService eventService; + private final RequestTransmitter transmitter; + private final ProjectManager projectManager; + private final EditorWorkingCopyManager editorWorkingCopyManager; + private final SemanticHighlightingReconciler semanticHighlighting; - private SemanticHighlightingReconciler semanticHighlighting; + private Mode mode = ACTIVATED; @Inject - public JavaReconciler(SemanticHighlightingReconciler semanticHighlighting) { + public JavaReconciler(SemanticHighlightingReconciler semanticHighlighting, + EventService eventService, + RequestTransmitter transmitter, + ProjectManager projectManager, + EditorWorkingCopyManager editorWorkingCopyManager) { this.semanticHighlighting = semanticHighlighting; - } + this.eventService = eventService; + this.transmitter = transmitter; + this.projectManager = projectManager; + this.editorWorkingCopyManager = editorWorkingCopyManager; - public ReconcileResult reconcile(IJavaProject javaProject, String fqn) throws JavaModelException { - final ProblemRequestor requestor = new ProblemRequestor(); - WorkingCopyOwner wcOwner = new WorkingCopyOwner() { - public IProblemRequestor getProblemRequestor(ICompilationUnit unit) { - return requestor; + EventSubscriber fileOperationEventSubscriber = new EventSubscriber() { + @Override + public void onEvent(FileTrackingOperationEvent event) { + onFileOperation(event.getEndpointId(), event.getFileTrackingOperation()); } + }; + eventService.subscribe(fileOperationEventSubscriber); + subscribers.add(fileOperationEventSubscriber); + EventSubscriber editorContentUpdateEventSubscriber = new EventSubscriber() { @Override - public IBuffer createBuffer(ICompilationUnit workingCopy) { -// return BufferManager.createBuffer(workingCopy); -// ????? - return new org.eclipse.jdt.internal.ui.javaeditor.DocumentAdapter(workingCopy, (IFile)workingCopy.getResource()); + public void onEvent(EditorWorkingCopyUpdatedEvent event) { + onEditorContentUpdated(event); } }; - List positions = null; - ICompilationUnit compilationUnit = null; + eventService.subscribe(editorContentUpdateEventSubscriber); + subscribers.add(editorContentUpdateEventSubscriber); + } + + @PreDestroy + private void unsubscribe() { + subscribers.forEach(eventService::unsubscribe); + } + + public ReconcileResult reconcile(IJavaProject javaProject, String fqn) throws JavaModelException { + IType type = getType(fqn, javaProject); + ICompilationUnit compilationUnit = type.getCompilationUnit(); + + return reconcile(compilationUnit, javaProject); + } + + private ReconcileResult reconcile(ICompilationUnit compilationUnit, IJavaProject javaProject) throws JavaModelException { + ICompilationUnit workingCopy = null; + List positions; + String filePath = compilationUnit.getPath().toString(); + + final ProblemRequestor problemRequestor = new ProblemRequestor(); + final WorkingCopyOwner wcOwner = createWorkingCopyOwner(problemRequestor); + try { - IType type = javaProject.findType(fqn); - if (type == null) { - return null; - } - if (type.isBinary()) { - throw new IllegalArgumentException("Can't reconcile binary type: " + fqn); - } else { - compilationUnit = type.getCompilationUnit().getWorkingCopy(wcOwner, null); - } - requestor.reset(); - CompilationUnit unit = compilationUnit.reconcile(AST.JLS8, true, wcOwner, null); + workingCopy = compilationUnit.getWorkingCopy(wcOwner, null); + synchronizeWorkingCopyContent(filePath, workingCopy); + problemRequestor.reset(); + + CompilationUnit unit = workingCopy.reconcile(AST.JLS8, true, wcOwner, null); positions = semanticHighlighting.reconcileSemanticHighlight(unit); - if (compilationUnit instanceof ClassFileWorkingCopy) { + + if (workingCopy instanceof ClassFileWorkingCopy) { //we don't wont to show any errors from ".class" files - requestor.reset(); + problemRequestor.reset(); } - } catch (JavaModelException e) { - LOG.error("Can't reconcile class: " + fqn + " in project:" + javaProject.getPath().toOSString(), e); + LOG.error(format("Can't reconcile class: %s in project: %s", filePath, javaProject.getPath().toOSString()), e); throw e; } finally { - if(compilationUnit!= null && compilationUnit.isWorkingCopy()){ + if (workingCopy != null && workingCopy.isWorkingCopy()) { try { - //todo close buffer - compilationUnit.getBuffer().close(); - compilationUnit.discardWorkingCopy(); + workingCopy.getBuffer().close(); + workingCopy.discardWorkingCopy(); } catch (JavaModelException e) { //ignore } } } - ReconcileResult result = DtoFactory.getInstance().createDto(ReconcileResult.class); - result.setProblems(convertProblems(requestor.problems)); - result.setHighlightedPositions(positions); - return result; + DtoFactory dtoFactory = DtoFactory.getInstance(); + return dtoFactory.createDto(ReconcileResult.class) + .withFileLocation(compilationUnit.getPath().toOSString()) + .withProblems(convertProblems(problemRequestor.problems)) + .withHighlightedPositions(positions); + } + + private void synchronizeWorkingCopyContent(String filePath, ICompilationUnit workingCopy) throws JavaModelException { + EditorWorkingCopy editorWorkingCopy = editorWorkingCopyManager.getWorkingCopy(filePath); + if (editorWorkingCopy == null) { + return; + } + + String oldContent = workingCopy.getBuffer().getContents(); + String newContent = editorWorkingCopy.getContentAsString(); + + TextEdit textEdit = new ReplaceEdit(0, oldContent.length(), newContent); + workingCopy.applyTextEdit(textEdit, null); + } + + private void onEditorContentUpdated(EditorWorkingCopyUpdatedEvent event) { + if (mode == DEACTIVATED) { + return; + } + + String endpointId = event.getEndpointId(); + EditorChangesDto editorChanges = event.getChanges(); + String filePath = editorChanges.getFileLocation(); + String projectPath = editorChanges.getProjectPath(); + + reconcileAndTransmit(filePath, projectPath, endpointId); + } + + private void onFileOperation(String endpointId, FileTrackingOperationDto operation) { + try { + Type operationType = operation.getType(); + switch (operationType) { + case START: { + String filePath = operation.getPath(); + VirtualFileEntry fileEntry = projectManager.getProjectsRoot().getChild(filePath); + if (fileEntry == null) { + throw new NotFoundException("The file is not found by path " + filePath); + } + + String projectPath = fileEntry.getProject(); + if (isNullOrEmpty(projectPath)) { + throw new NotFoundException("The project is not recognized for " + filePath); + } + + reconcileAndTransmit(filePath, projectPath, endpointId); + break; + } + + case SUSPEND: { + mode = DEACTIVATED; + break; + } + + case RESUME: { + mode = ACTIVATED; + break; + } + + default: { + break; + } + } + } catch (ServerException e) { + String errorMessage = "Can not handle file operation: " + e.getMessage(); + + LOG.error(errorMessage); + + transmitError(500, errorMessage, endpointId); + } catch (NotFoundException e) { + String errorMessage = "Can not handle file operation: " + e.getMessage(); + + LOG.error(errorMessage); + + transmitError(400, errorMessage, endpointId); + } + } + + private void reconcileAndTransmit(String filePath, String projectPath, String endpointId) { + ICompilationUnit compilationUnit; + try { + compilationUnit = getCompilationUnit(filePath, projectPath); + if (compilationUnit == null) { + return; + } + } catch (JavaModelException e) { + return; //ignore - we haven't compilation unit to reconcile + } + + try { + ReconcileResult reconcileResult = reconcile(compilationUnit, getJavaProject(projectPath)); + transmitter.newRequest() + .endpointId(endpointId) + .methodName(RECONCILE_STATE_CHANGED_METHOD) + .paramsAsDto(reconcileResult) + .sendAndSkipResult(); + } catch (JavaModelException e) { + String errorMessage = + format("Can't reconcile class: %s in project: %s, the reason is %s", filePath, projectPath, e.getLocalizedMessage()); + + LOG.error(errorMessage); + + transmitError(500, errorMessage, endpointId); + } + } + + private void transmitError(int code, String errorMessage, String endpointId) { + DtoFactory dtoFactory = DtoFactory.getInstance(); + ServerError reconcileError = dtoFactory.createDto(ServerError.class) + .withCode(code) + .withMessage(errorMessage); + transmitter.newRequest() + .endpointId(endpointId) + .methodName(RECONCILE_ERROR_METHOD) + .paramsAsDto(reconcileError) + .sendAndSkipResult(); + } + + @Nullable + private ICompilationUnit getCompilationUnit(String filePath, String projectPath) throws JavaModelException { + IJavaProject javaProject = getJavaProject(projectPath); + if (javaProject == null) { + return null; + } + + List classpathEntries = asList(javaProject.getRawClasspath()); + for (IClasspathEntry classpathEntry : classpathEntries) { + String entryPath = classpathEntry.getPath().toString(); + if (!filePath.contains(entryPath)) { + continue; + } + + String fileRelativePath = filePath.substring(entryPath.length() + 1); + IJavaElement javaElement = javaProject.findElement(new Path(fileRelativePath)); + if (javaElement == null) { + continue; + } + + int elementType = javaElement.getElementType(); + if (COMPILATION_UNIT == elementType) { + return (ICompilationUnit)javaElement; + } + } + return null; + } + + @Nullable + private IJavaProject getJavaProject(String projectPath) throws JavaModelException { + IJavaProject project = JAVA_MODEL.getJavaProject(projectPath); + List javaProjects = asList(JAVA_MODEL.getJavaProjects()); + + return javaProjects.contains(project) ? project : null; + } + + private WorkingCopyOwner createWorkingCopyOwner(ProblemRequestor problemRequestor) { + return new WorkingCopyOwner() { + public IProblemRequestor getProblemRequestor(ICompilationUnit unit) { + return problemRequestor; + } + + @Override + public IBuffer createBuffer(ICompilationUnit workingCopy) { + return new DocumentAdapter(workingCopy, (IFile)workingCopy.getResource()); + } + }; } private List convertProblems(List problems) { @@ -118,7 +349,7 @@ private List convertProblems(List problems) { private Problem convertProblem(IProblem problem) { Problem result = DtoFactory.getInstance().createDto(Problem.class); - result.setArguments(Arrays.asList(problem.getArguments())); + result.setArguments(asList(problem.getArguments())); result.setID(problem.getID()); result.setMessage(problem.getMessage()); result.setOriginatingFileName(new String(problem.getOriginatingFileName())); @@ -131,7 +362,21 @@ private Problem convertProblem(IProblem problem) { return result; } - private static class ProblemRequestor implements IProblemRequestor { + private IType getType(String fqn, IJavaProject javaProject) throws JavaModelException { + checkState(!isNullOrEmpty(fqn), "Incorrect fully qualified name is specified"); + + final IType type = javaProject.findType(fqn); + if (type == null) { + throw new JavaModelException(new Throwable("Can not find type for " + fqn), 500); + } + + if (type.isBinary()) { + throw new JavaModelException(new Throwable("Can't reconcile binary type: " + fqn), 500); + } + return type; + } + + private class ProblemRequestor implements IProblemRequestor { private List problems = new ArrayList<>(); @@ -159,4 +404,14 @@ public void reset() { problems.clear(); } } + + enum Mode { + /** The state when the reconciler is turned on. */ + ACTIVATED, + /** + * The state when the reconciler is turned off + * for processing reconcile operation (while java refactoring in progress, for example) + */ + DEACTIVATED + } } diff --git a/plugins/plugin-java/che-plugin-java-ext-lang-client/pom.xml b/plugins/plugin-java/che-plugin-java-ext-lang-client/pom.xml index ab132fcabbd..27d3eb46aa1 100644 --- a/plugins/plugin-java/che-plugin-java-ext-lang-client/pom.xml +++ b/plugins/plugin-java/che-plugin-java-ext-lang-client/pom.xml @@ -50,6 +50,14 @@ javax.validation validation-api + + org.eclipse.che.core + che-core-api-core + + + org.eclipse.che.core + che-core-api-project-shared + org.eclipse.che.core che-core-commons-annotations diff --git a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/JavaLocalizationConstant.java b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/JavaLocalizationConstant.java index b3ddb5dfc7b..7c52f87e2c6 100644 --- a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/JavaLocalizationConstant.java +++ b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/JavaLocalizationConstant.java @@ -384,6 +384,12 @@ public interface JavaLocalizationConstant extends Messages { @Key("unsavedChanges.title") String unsavedChangesTitle(); + @Key("unsavedDataDialog.title") + String unsavedDataDialogTitle(); + + @Key("unsavedDataDialog.promptSaveChanges") + String unsavedDataDialogPromptSaveChanges(); + @Key("button.done") String buttonDone(); diff --git a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/editor/JavaReconcileClient.java b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/editor/JavaReconcileClient.java index 4bccc4ed21c..2cecde8b8a9 100644 --- a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/editor/JavaReconcileClient.java +++ b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/editor/JavaReconcileClient.java @@ -13,7 +13,11 @@ import com.google.inject.Inject; import com.google.inject.Singleton; +import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcPromise; +import org.eclipse.che.api.core.jsonrpc.commons.RequestTransmitter; import org.eclipse.che.ide.api.app.AppContext; +import org.eclipse.che.ide.dto.DtoFactory; +import org.eclipse.che.ide.ext.java.shared.dto.JavaClassInfo; import org.eclipse.che.ide.ext.java.shared.dto.ReconcileResult; import org.eclipse.che.ide.rest.AsyncRequestCallback; import org.eclipse.che.ide.rest.AsyncRequestFactory; @@ -25,20 +29,50 @@ */ @Singleton public class JavaReconcileClient { + private static final String ENDPOINT_ID = "ws-agent"; + private static final String OUTCOMING_METHOD = "request:java-reconcile"; + private final RequestTransmitter requestTransmitter; private final DtoUnmarshallerFactory dtoUnmarshallerFactory; private final AsyncRequestFactory asyncRequestFactory; - private final AppContext appContext; + private final AppContext appContext; + private DtoFactory dtoFactory; @Inject public JavaReconcileClient(DtoUnmarshallerFactory dtoUnmarshallerFactory, AppContext appContext, - AsyncRequestFactory asyncRequestFactory) { + RequestTransmitter requestTransmitter, + AsyncRequestFactory asyncRequestFactory, + DtoFactory dtoFactory) { this.appContext = appContext; this.dtoUnmarshallerFactory = dtoUnmarshallerFactory; this.asyncRequestFactory = asyncRequestFactory; + this.requestTransmitter = requestTransmitter; + this.dtoFactory = dtoFactory; } + /** + * Sends request on server side to reconcile entity by given fqn + * + * @param fqn + * fully qualified name of entity to reconcile + * @param projectPath + * path to the project which contains the entity to reconcile + * @return promise which represents result of reconcile operation + */ + public JsonRpcPromise reconcile(String fqn, String projectPath) { + JavaClassInfo javaClassInfo = dtoFactory.createDto(JavaClassInfo.class) + .withFQN(fqn) + .withProjectPath(projectPath); + return requestTransmitter.newRequest() + .endpointId(ENDPOINT_ID) + .methodName(OUTCOMING_METHOD) + .paramsAsDto(javaClassInfo) + .sendAndReceiveResultAsDto(ReconcileResult.class); + } + + /** @deprecated in favor of {@link #reconcile(String, String)} */ + @Deprecated public void reconcile(String projectPath, String fqn, final ReconcileCallback callback) { String url = appContext.getDevMachine().getWsAgentBaseUrl() + "/java/reconcile/?projectpath=" + projectPath + "&fqn=" + fqn; asyncRequestFactory.createGetRequest(url) @@ -55,6 +89,8 @@ protected void onFailure(Throwable exception) { }); } + /** @deprecated use {@link #reconcile(String, String)} which does not use callback */ + @Deprecated public interface ReconcileCallback { void onReconcile(ReconcileResult result); } diff --git a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/editor/JavaReconcileUpdateOperation.java b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/editor/JavaReconcileUpdateOperation.java new file mode 100644 index 00000000000..688fada4ba3 --- /dev/null +++ b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/editor/JavaReconcileUpdateOperation.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * 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.ext.java.client.editor; + +import com.google.web.bindery.event.shared.EventBus; + +import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerConfigurator; +import org.eclipse.che.api.project.shared.dto.ServerError; +import org.eclipse.che.ide.ext.java.shared.dto.ReconcileResult; +import org.eclipse.che.ide.util.loging.Log; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Receives result of reconcile operations from server side and notifies interested client consumers about it. + * + * @author Roman Nikitenko + */ +@Singleton +public class JavaReconcileUpdateOperation { + private static final String JAVA_RECONCILE_ERROR_METHOD = "event:java-reconcile-error"; + private static final String JAVA_RECONCILE_STATE_CHANGED_METHOD = "event:java-reconcile-state-changed"; + + private final EventBus eventBus; + + @Inject + public JavaReconcileUpdateOperation(EventBus eventBus) { + this.eventBus = eventBus; + } + + @Inject + public void configureHandler(RequestHandlerConfigurator configurator) { + configurator.newConfiguration() + .methodName(JAVA_RECONCILE_ERROR_METHOD) + .paramsAsDto(ServerError.class) + .noResult() + .withConsumer(this::onError); + + configurator.newConfiguration() + .methodName(JAVA_RECONCILE_STATE_CHANGED_METHOD) + .paramsAsDto(ReconcileResult.class) + .noResult() + .withConsumer(this::onSuccess); + } + + private void onSuccess(ReconcileResult reconcileResult) { + eventBus.fireEvent(new ReconcileOperationEvent(reconcileResult)); + } + + private void onError(ServerError reconcileError) { + Log.error(getClass(), reconcileError.getMessage()); + } +} diff --git a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/editor/JavaReconcilerStrategy.java b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/editor/JavaReconcilerStrategy.java index a005a1f6b44..f50204696e8 100644 --- a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/editor/JavaReconcilerStrategy.java +++ b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/editor/JavaReconcilerStrategy.java @@ -13,6 +13,8 @@ import com.google.common.base.Optional; import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.AssistedInject; +import com.google.web.bindery.event.shared.EventBus; +import com.google.web.bindery.event.shared.HandlerRegistration; import org.eclipse.che.ide.api.editor.EditorWithErrors; import org.eclipse.che.ide.api.editor.annotation.AnnotationModel; @@ -25,6 +27,7 @@ import org.eclipse.che.ide.api.resources.Resource; import org.eclipse.che.ide.api.resources.VirtualFile; import org.eclipse.che.ide.ext.java.client.JavaLocalizationConstant; +import org.eclipse.che.ide.ext.java.client.editor.ReconcileOperationEvent.ReconcileOperationHandler; import org.eclipse.che.ide.ext.java.client.util.JavaUtil; import org.eclipse.che.ide.ext.java.shared.dto.HighlightedPosition; import org.eclipse.che.ide.ext.java.shared.dto.Problem; @@ -37,12 +40,12 @@ import javax.validation.constraints.NotNull; import java.util.Collections; +import java.util.HashSet; import java.util.List; import static org.eclipse.che.ide.project.ResolvingProjectStateHolder.ResolvingProjectState.IN_PROGRESS; -public class JavaReconcilerStrategy implements ReconcilingStrategy, ResolvingProjectStateListener { - +public class JavaReconcilerStrategy implements ReconcilingStrategy, ResolvingProjectStateListener, ReconcileOperationHandler { private final TextEditor editor; private final JavaCodeAssistProcessor codeAssistProcessor; private final AnnotationModel annotationModel; @@ -53,6 +56,7 @@ public class JavaReconcilerStrategy implements ReconcilingStrategy, ResolvingPro private EditorWithErrors editorWithErrors; private ResolvingProjectStateHolder resolvingProjectStateHolder; + private HashSet handlerRegistrations = new HashSet<>(2); @AssistedInject public JavaReconcilerStrategy(@Assisted @NotNull final TextEditor editor, @@ -61,7 +65,8 @@ public JavaReconcilerStrategy(@Assisted @NotNull final TextEditor editor, final JavaReconcileClient client, final SemanticHighlightRenderer highlighter, final ResolvingProjectStateHolderRegistry resolvingProjectStateHolderRegistry, - final JavaLocalizationConstant localizationConstant) { + final JavaLocalizationConstant localizationConstant, + final EventBus eventBus) { this.editor = editor; this.client = client; this.codeAssistProcessor = codeAssistProcessor; @@ -72,6 +77,9 @@ public JavaReconcilerStrategy(@Assisted @NotNull final TextEditor editor, if (editor instanceof EditorWithErrors) { this.editorWithErrors = ((EditorWithErrors)editor); } + + HandlerRegistration reconcileOperationHandlerRegistration = eventBus.addHandler(ReconcileOperationEvent.TYPE, this); + handlerRegistrations.add(reconcileOperationHandlerRegistration); } @Override @@ -92,7 +100,7 @@ public void setDocument(final Document document) { } resolvingProjectStateHolder.addResolvingProjectStateListener(this); - if (resolvingProjectStateHolder.getState() == IN_PROGRESS) { + if (isProjectResolving()) { disableReconciler(localizationConstant.codeAssistErrorMessageResolvingProject()); } } @@ -111,31 +119,27 @@ void parse() { return; } - try { - client.reconcile(project.get().getLocation().toString(), JavaUtil.resolveFQN(getFile()), - new JavaReconcileClient.ReconcileCallback() { - @Override - public void onReconcile(ReconcileResult result) { - if (resolvingProjectStateHolder != null && resolvingProjectStateHolder.getState() == IN_PROGRESS) { - disableReconciler(localizationConstant.codeAssistErrorMessageResolvingProject()); - return; - } else { - codeAssistProcessor.enableCodeAssistant(); - } - - if (result == null) { - return; - } - doReconcile(result.getProblems()); - highlighter.reconcile(result.getHighlightedPositions()); - } - }); - } catch (RuntimeException e) { - Log.info(getClass(), e.getMessage()); - } - } + String fqn = JavaUtil.resolveFQN(getFile()); + String projectPath = project.get().getLocation().toString(); + client.reconcile(fqn, projectPath).onSuccess(reconcileResult -> { + if (isProjectResolving()) { + disableReconciler(localizationConstant.codeAssistErrorMessageResolvingProject()); + return; + } else { + codeAssistProcessor.enableCodeAssistant(); + } + if (reconcileResult == null) { + return; + } + + doReconcile(reconcileResult.getProblems()); + highlighter.reconcile(reconcileResult.getHighlightedPositions()); + }).onFailure(jsonRpcError -> { + Log.info(getClass(), jsonRpcError.getMessage()); + }); + } } @@ -175,7 +179,7 @@ private void doReconcile(final List problems) { } problemRequester.acceptProblem(problem); } - if(editorWithErrors != null) { + if (editorWithErrors != null) { if (error) { editorWithErrors.setErrorState(EditorWithErrors.EditorState.ERROR); } else if (warning) { @@ -202,6 +206,7 @@ public void closeReconciler() { if (resolvingProjectStateHolder != null) { resolvingProjectStateHolder.removeResolvingProjectStateListener(this); } + handlerRegistrations.forEach(HandlerRegistration::removeHandler); } @Override @@ -217,4 +222,26 @@ public void onResolvingProjectStateChanged(ResolvingProjectState state) { break; } } + + private boolean isProjectResolving() { + return resolvingProjectStateHolder != null && resolvingProjectStateHolder.getState() == IN_PROGRESS; + } + + @Override + public void onReconcileOperation(ReconcileResult reconcileResult) { + String currentEditorPath = editor.getEditorInput().getFile().getLocation().toString(); + if (!currentEditorPath.equals(reconcileResult.getFileLocation())) { + return; + } + + if (isProjectResolving()) { + disableReconciler(localizationConstant.codeAssistErrorMessageResolvingProject()); + return; + } else { + codeAssistProcessor.enableCodeAssistant(); + } + + doReconcile(reconcileResult.getProblems()); + highlighter.reconcile(reconcileResult.getHighlightedPositions()); + } } diff --git a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/editor/JsJavaEditorProvider.java b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/editor/JsJavaEditorProvider.java index 6a2ce4f6c33..694f6cdecc2 100644 --- a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/editor/JsJavaEditorProvider.java +++ b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/editor/JsJavaEditorProvider.java @@ -11,18 +11,13 @@ package org.eclipse.che.ide.ext.java.client.editor; import org.eclipse.che.ide.api.editor.defaulteditor.AbstractTextEditorProvider; -import org.eclipse.che.ide.api.editor.editorconfig.EditorUpdateAction; import org.eclipse.che.ide.api.editor.editorconfig.TextEditorConfiguration; -import org.eclipse.che.ide.api.editor.reconciler.Reconciler; -import org.eclipse.che.ide.api.editor.reconciler.ReconcilingStrategy; import org.eclipse.che.ide.api.editor.texteditor.TextEditor; import org.eclipse.che.ide.editor.orion.client.OrionEditorPresenter; import javax.inject.Inject; import java.util.logging.Logger; -import static org.eclipse.che.ide.api.editor.partition.DocumentPartitioner.DEFAULT_CONTENT_TYPE; - /** EditorProvider that provides a text editor configured for java source files. */ public class JsJavaEditorProvider extends AbstractTextEditorProvider { @@ -57,18 +52,6 @@ public TextEditor getEditor() { final OrionEditorPresenter editor = (OrionEditorPresenter)textEditor; final TextEditorConfiguration configuration = configurationFactory.create(editor); editor.initialize(configuration); - editor.addEditorUpdateAction(new EditorUpdateAction() { - @Override - public void doRefresh() { - final Reconciler reconciler = configuration.getReconciler(); - if (reconciler != null) { - final ReconcilingStrategy strategy = reconciler.getReconcilingStrategy(DEFAULT_CONTENT_TYPE); - if (strategy instanceof JavaReconcilerStrategy) { - ((JavaReconcilerStrategy)strategy).parse(); - } - } - } - }); } watcher.editorOpened(textEditor); diff --git a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/editor/ReconcileOperationEvent.java b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/editor/ReconcileOperationEvent.java new file mode 100755 index 00000000000..492aec5e1ed --- /dev/null +++ b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/editor/ReconcileOperationEvent.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * 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.ext.java.client.editor; + +import com.google.gwt.event.shared.EventHandler; +import com.google.gwt.event.shared.GwtEvent; + +import org.eclipse.che.ide.ext.java.shared.dto.ReconcileResult; + +/** + * The event is used to notify interested client consumers about results of reconcile operation. + * + * @author Roman Nikitenko + */ +public class ReconcileOperationEvent extends GwtEvent { + public static Type TYPE = new Type<>(); + + private final ReconcileResult reconcileResult; + + /** + * Creates an event which contains result of reconcile operation + * + * @param reconcileResult + * info about result of reconcile operation + */ + public ReconcileOperationEvent(ReconcileResult reconcileResult) { + this.reconcileResult = reconcileResult; + } + + @Override + public Type getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(ReconcileOperationHandler handler) { + handler.onReconcileOperation(reconcileResult); + } + + /** Apples result of reconcile operation */ + public interface ReconcileOperationHandler extends EventHandler { + void onReconcileOperation(ReconcileResult result); + } +} diff --git a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/inject/JavaEditorGinModule.java b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/inject/JavaEditorGinModule.java index 48d436ac349..fabc94c5fa1 100644 --- a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/inject/JavaEditorGinModule.java +++ b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/inject/JavaEditorGinModule.java @@ -10,19 +10,20 @@ *******************************************************************************/ package org.eclipse.che.ide.ext.java.client.inject; +import com.google.gwt.inject.client.AbstractGinModule; +import com.google.gwt.inject.client.assistedinject.GinFactoryModuleBuilder; + +import org.eclipse.che.ide.api.editor.formatter.ContentFormatter; import org.eclipse.che.ide.api.extension.ExtensionGinModule; import org.eclipse.che.ide.ext.java.client.editor.JavaAnnotationModelFactory; import org.eclipse.che.ide.ext.java.client.editor.JavaCodeAssistProcessorFactory; +import org.eclipse.che.ide.ext.java.client.editor.JavaFormatter; import org.eclipse.che.ide.ext.java.client.editor.JavaPartitionScanner; import org.eclipse.che.ide.ext.java.client.editor.JavaPartitionerFactory; import org.eclipse.che.ide.ext.java.client.editor.JavaQuickAssistProcessorFactory; +import org.eclipse.che.ide.ext.java.client.editor.JavaReconcileUpdateOperation; import org.eclipse.che.ide.ext.java.client.editor.JavaReconcilerStrategyFactory; import org.eclipse.che.ide.ext.java.client.editor.JsJavaEditorConfigurationFactory; -import org.eclipse.che.ide.ext.java.client.editor.JavaFormatter; -import org.eclipse.che.ide.api.editor.formatter.ContentFormatter; - -import com.google.gwt.inject.client.AbstractGinModule; -import com.google.gwt.inject.client.assistedinject.GinFactoryModuleBuilder; @ExtensionGinModule public class JavaEditorGinModule extends AbstractGinModule { @@ -37,5 +38,6 @@ protected void configure() { bind(ContentFormatter.class).to(JavaFormatter.class); bind(JavaPartitionScanner.class); bind(JavaPartitionerFactory.class); + bind(JavaReconcileUpdateOperation.class).asEagerSingleton(); } } diff --git a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/refactoring/move/MoveAction.java b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/refactoring/move/MoveAction.java index 95afd03f56f..7d958c8250c 100644 --- a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/refactoring/move/MoveAction.java +++ b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/refactoring/move/MoveAction.java @@ -11,12 +11,16 @@ package org.eclipse.che.ide.ext.java.client.refactoring.move; import com.google.common.base.Optional; +import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.inject.Inject; import com.google.inject.Singleton; import org.eclipse.che.ide.api.action.AbstractPerspectiveAction; import org.eclipse.che.ide.api.action.ActionEvent; import org.eclipse.che.ide.api.app.AppContext; +import org.eclipse.che.ide.api.dialogs.DialogFactory; +import org.eclipse.che.ide.api.editor.EditorAgent; +import org.eclipse.che.ide.api.editor.EditorPartPresenter; import org.eclipse.che.ide.api.filetypes.FileTypeRegistry; import org.eclipse.che.ide.api.resources.Container; import org.eclipse.che.ide.api.resources.File; @@ -28,6 +32,9 @@ import org.eclipse.che.ide.ext.java.client.refactoring.move.wizard.MovePresenter; import org.eclipse.che.ide.ext.java.client.resource.SourceFolderMarker; import org.eclipse.che.ide.ext.java.client.util.JavaUtil; +import org.eclipse.che.ide.util.loging.Log; + +import java.util.List; import static org.eclipse.che.ide.api.resources.Resource.FILE; import static org.eclipse.che.ide.ext.java.client.refactoring.move.MoveType.REFACTOR_MENU; @@ -41,20 +48,28 @@ @Singleton public class MoveAction extends AbstractPerspectiveAction { - private final MovePresenter movePresenter; - private final AppContext appContext; - private final FileTypeRegistry fileTypeRegistry; + private final JavaLocalizationConstant locale; + private final MovePresenter movePresenter; + private final AppContext appContext; + private final FileTypeRegistry fileTypeRegistry; + private final DialogFactory dialogFactory; + private EditorAgent editorAgent; @Inject public MoveAction(JavaLocalizationConstant locale, MovePresenter movePresenter, AppContext appContext, - FileTypeRegistry fileTypeRegistry) { + FileTypeRegistry fileTypeRegistry, + DialogFactory dialogFactory, + EditorAgent editorAgent) { super(null, locale.moveActionName(), locale.moveActionDescription()); + this.locale = locale; this.movePresenter = movePresenter; this.appContext = appContext; this.fileTypeRegistry = fileTypeRegistry; + this.dialogFactory = dialogFactory; + this.editorAgent = editorAgent; } /** {@inheritDoc} */ @@ -81,7 +96,8 @@ public void updateInPerspective(ActionEvent event) { final Optional srcFolder = resource.getParentWithMarker(SourceFolderMarker.ID); if (resource.getResourceType() == FILE) { - event.getPresentation().setEnabled(JavaUtil.isJavaProject(project.get()) && srcFolder.isPresent() && isJavaFile((File)resource)); + event.getPresentation() + .setEnabled(JavaUtil.isJavaProject(project.get()) && srcFolder.isPresent() && isJavaFile((File)resource)); } else if (resource instanceof Container) { event.getPresentation().setEnabled(JavaUtil.isJavaProject(project.get()) && srcFolder.isPresent()); } @@ -90,6 +106,32 @@ public void updateInPerspective(ActionEvent event) { /** {@inheritDoc} */ @Override public void actionPerformed(ActionEvent actionEvent) { + List dirtyEditors = editorAgent.getDirtyEditors(); + if (dirtyEditors.isEmpty()) { + performAction(); + return; + } + + AsyncCallback savingOperationCallback = new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + Log.error(getClass(), caught); + } + + @Override + public void onSuccess(Void result) { + performAction(); + } + }; + + dialogFactory.createConfirmDialog(locale.unsavedDataDialogTitle(), + locale.unsavedDataDialogPromptSaveChanges(), + () -> editorAgent.saveAll(savingOperationCallback), + null) + .show(); + } + + private void performAction() { final Resource[] resources = appContext.getResources(); if (resources == null || resources.length > 1) { diff --git a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/refactoring/rename/RenameRefactoringAction.java b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/refactoring/rename/RenameRefactoringAction.java index d40b51ad579..055c4039155 100644 --- a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/refactoring/rename/RenameRefactoringAction.java +++ b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/refactoring/rename/RenameRefactoringAction.java @@ -11,6 +11,7 @@ package org.eclipse.che.ide.ext.java.client.refactoring.rename; import com.google.common.base.Optional; +import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.inject.Inject; import com.google.inject.Singleton; import com.google.web.bindery.event.shared.EventBus; @@ -18,6 +19,7 @@ import org.eclipse.che.ide.api.action.AbstractPerspectiveAction; import org.eclipse.che.ide.api.action.ActionEvent; import org.eclipse.che.ide.api.app.AppContext; +import org.eclipse.che.ide.api.dialogs.DialogFactory; import org.eclipse.che.ide.api.editor.EditorAgent; import org.eclipse.che.ide.api.editor.EditorPartPresenter; import org.eclipse.che.ide.api.editor.texteditor.TextEditor; @@ -35,6 +37,9 @@ import org.eclipse.che.ide.ext.java.client.refactoring.rename.wizard.RenamePresenter; import org.eclipse.che.ide.ext.java.client.resource.SourceFolderMarker; import org.eclipse.che.ide.ext.java.client.util.JavaUtil; +import org.eclipse.che.ide.util.loging.Log; + +import java.util.List; import static org.eclipse.che.ide.api.resources.Resource.FILE; import static org.eclipse.che.ide.ext.java.client.refactoring.move.RefactoredItemType.COMPILATION_UNIT; @@ -50,11 +55,13 @@ @Singleton public class RenameRefactoringAction extends AbstractPerspectiveAction implements ActivePartChangedHandler { - private final EditorAgent editorAgent; - private final RenamePresenter renamePresenter; - private final JavaRefactoringRename javaRefactoringRename; - private final AppContext appContext; - private final FileTypeRegistry fileTypeRegistry; + private final EditorAgent editorAgent; + private final RenamePresenter renamePresenter; + private final JavaLocalizationConstant locale; + private final JavaRefactoringRename javaRefactoringRename; + private final AppContext appContext; + private final FileTypeRegistry fileTypeRegistry; + private final DialogFactory dialogFactory; private boolean editorInFocus; @@ -65,13 +72,16 @@ public RenameRefactoringAction(EditorAgent editorAgent, JavaLocalizationConstant locale, JavaRefactoringRename javaRefactoringRename, AppContext appContext, - FileTypeRegistry fileTypeRegistry) { + FileTypeRegistry fileTypeRegistry, + DialogFactory dialogFactory) { super(null, locale.renameRefactoringActionName(), locale.renameRefactoringActionDescription()); this.editorAgent = editorAgent; this.renamePresenter = renamePresenter; + this.locale = locale; this.javaRefactoringRename = javaRefactoringRename; this.appContext = appContext; this.fileTypeRegistry = fileTypeRegistry; + this.dialogFactory = dialogFactory; this.editorInFocus = false; eventBus.addHandler(ActivePartChangedEvent.TYPE, this); @@ -79,7 +89,32 @@ public RenameRefactoringAction(EditorAgent editorAgent, @Override public void actionPerformed(ActionEvent event) { + List dirtyEditors = editorAgent.getDirtyEditors(); + if (dirtyEditors.isEmpty()) { + performAction(); + return; + } + + AsyncCallback savingOperationCallback = new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + Log.error(getClass(), caught); + } + + @Override + public void onSuccess(Void result) { + performAction(); + } + }; + + dialogFactory.createConfirmDialog(locale.unsavedDataDialogTitle(), + locale.unsavedDataDialogPromptSaveChanges(), + () -> editorAgent.saveAll(savingOperationCallback), + null) + .show(); + } + private void performAction() { if (editorInFocus) { final EditorPartPresenter editorPart = editorAgent.getActiveEditor(); if (editorPart == null || !(editorPart instanceof TextEditor)) { diff --git a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/resources/org/eclipse/che/ide/ext/java/client/JavaLocalizationConstant.properties b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/resources/org/eclipse/che/ide/ext/java/client/JavaLocalizationConstant.properties index 3a590d02a8d..23cae5ec7a0 100644 --- a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/resources/org/eclipse/che/ide/ext/java/client/JavaLocalizationConstant.properties +++ b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/resources/org/eclipse/che/ide/ext/java/client/JavaLocalizationConstant.properties @@ -163,6 +163,8 @@ libraries.property.name=Libraries library.title=JARs and class folders on the build path messages.promptSaveChanges = You have unsaved changes in classpath configuration that will be lost if you decide to continue. Are you sure you want to continue? unsavedChanges.title = Confirm Navigation +unsavedDataDialog.title = Unsaved data +unsavedDataDialog.promptSaveChanges = You have unsaved edit changes. Do you want to save these changes? button.addJar=Add JAR button.done=Done button.Save=Save and close diff --git a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/test/java/org/eclipse/che/ide/ext/java/client/editor/JavaReconcilerStrategyTest.java b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/test/java/org/eclipse/che/ide/ext/java/client/editor/JavaReconcilerStrategyTest.java index 13997738f78..8c3ea7e3150 100644 --- a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/test/java/org/eclipse/che/ide/ext/java/client/editor/JavaReconcilerStrategyTest.java +++ b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/test/java/org/eclipse/che/ide/ext/java/client/editor/JavaReconcilerStrategyTest.java @@ -14,6 +14,7 @@ import com.google.web.bindery.event.shared.EventBus; import com.google.web.bindery.event.shared.HandlerRegistration; +import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcPromise; import org.eclipse.che.ide.api.editor.EditorInput; import org.eclipse.che.ide.api.editor.annotation.AnnotationModel; import org.eclipse.che.ide.api.editor.document.Document; @@ -34,12 +35,14 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.InjectMocks; +import org.mockito.Matchers; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.function.Consumer; import static org.eclipse.che.ide.project.ResolvingProjectStateHolder.ResolvingProjectState.IN_PROGRESS; import static org.eclipse.che.ide.project.ResolvingProjectStateHolder.ResolvingProjectState.RESOLVED; @@ -48,6 +51,7 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -57,6 +61,7 @@ @RunWith(MockitoJUnitRunner.class) public class JavaReconcilerStrategyTest { private static final String FILE_NAME = "TestClass.java"; + private static final String FILE_PATH = "some/path/to/file/TestClass.java"; @Mock private EventBus eventBus; @@ -82,9 +87,11 @@ public class JavaReconcilerStrategyTest { private ResolvingProjectStateHolderRegistry resolvingProjectStateHolderRegistry; @Mock private JavaLocalizationConstant localizationConstant; + @Mock + private JsonRpcPromise reconcileResultPromise; @Captor - private ArgumentCaptor reconcileCallbackCaptor; + private ArgumentCaptor> reconcileResultCaptor; @InjectMocks @@ -102,7 +109,7 @@ public void setUp() throws Exception { when(editorInput.getFile()).thenReturn(file); when(file.getName()).thenReturn(FILE_NAME); when(file.getRelatedProject()).thenReturn(project); - when(file.getLocation()).thenReturn(Path.valueOf("some/path/to/file")); + when(file.getLocation()).thenReturn(Path.valueOf(FILE_PATH)); when(project.get()).thenReturn(projectConfig); when(projectConfig.getLocation()).thenReturn(Path.valueOf("some/path/to/project")); @@ -115,6 +122,9 @@ public void setUp() throws Exception { when(resolvingProjectStateHolderRegistry.getResolvingProjectStateHolder(anyString())).thenReturn(resolvingProjectStateHolder); when(localizationConstant.codeAssistErrorMessageResolvingProject()).thenReturn("error"); + when(client.reconcile(anyString(), anyString())).thenReturn(reconcileResultPromise); + when(reconcileResultPromise.onSuccess(Matchers.>anyObject())).thenReturn(reconcileResultPromise); + javaReconcilerStrategy.setDocument(mock(Document.class)); } @@ -124,9 +134,8 @@ public void shouldDisableReconcilerWhenResolvingProjectIsInProgress() throws Exc javaReconcilerStrategy.parse(); - verify(client).reconcile(anyString(), anyString(), reconcileCallbackCaptor.capture()); - JavaReconcileClient.ReconcileCallback reconcileCallback = reconcileCallbackCaptor.getValue(); - reconcileCallback.onReconcile(reconcileResult); + verify(reconcileResultPromise).onSuccess(reconcileResultCaptor.capture()); + reconcileResultCaptor.getValue().accept(reconcileResult); verify(reconcileResult, never()).getProblems(); verify(reconcileResult, never()).getHighlightedPositions(); @@ -145,9 +154,40 @@ public void shouldDoParseWhenResolvingProjectHasResolved() throws Exception { javaReconcilerStrategy.parse(); - verify(client).reconcile(anyString(), anyString(), reconcileCallbackCaptor.capture()); - JavaReconcileClient.ReconcileCallback reconcileCallback = reconcileCallbackCaptor.getValue(); - reconcileCallback.onReconcile(reconcileResult); + verify(reconcileResultPromise).onSuccess(reconcileResultCaptor.capture()); + reconcileResultCaptor.getValue().accept(reconcileResult); + + verify(reconcileResult).getProblems(); + verify(reconcileResult).getHighlightedPositions(); + verify(codeAssistProcessor).enableCodeAssistant(); + verify(codeAssistProcessor, never()).disableCodeAssistant(anyString()); + verify(highlighter).reconcile(eq(positions)); + } + + @Test + public void shouldSkipReconcileResultByFilePath() throws Exception { + reset(resolvingProjectStateHolder); + when(reconcileResult.getFileLocation()).thenReturn("some/path/to/stranger/file"); + + javaReconcilerStrategy.onReconcileOperation(reconcileResult); + + verify(resolvingProjectStateHolder, never()).getState(); + verify(reconcileResult, never()).getProblems(); + verify(reconcileResult, never()).getHighlightedPositions(); + verify(codeAssistProcessor, never()).enableCodeAssistant(); + verify(codeAssistProcessor, never()).disableCodeAssistant(anyString()); + } + + @Test + public void shouldApplyReconcileResultAtReconcileOperation() throws Exception { + when(resolvingProjectStateHolder.getState()).thenReturn(RESOLVED); + HighlightedPosition highlightedPosition = mock(HighlightedPosition.class); + List positions = new ArrayList<>(); + positions.add(highlightedPosition); + when(reconcileResult.getHighlightedPositions()).thenReturn(positions); + when(reconcileResult.getFileLocation()).thenReturn(FILE_PATH); + + javaReconcilerStrategy.onReconcileOperation(reconcileResult); verify(reconcileResult).getProblems(); verify(reconcileResult).getHighlightedPositions(); diff --git a/plugins/plugin-java/che-plugin-java-ext-lang-server/src/main/java/org/eclipse/che/plugin/java/server/JavaReconcileRequestHandler.java b/plugins/plugin-java/che-plugin-java-ext-lang-server/src/main/java/org/eclipse/che/plugin/java/server/JavaReconcileRequestHandler.java new file mode 100644 index 00000000000..4a7d3f9fc13 --- /dev/null +++ b/plugins/plugin-java/che-plugin-java-ext-lang-server/src/main/java/org/eclipse/che/plugin/java/server/JavaReconcileRequestHandler.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * 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.plugin.java.server; + +import com.google.inject.Inject; + +import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcException; +import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerConfigurator; +import org.eclipse.che.ide.ext.java.shared.dto.JavaClassInfo; +import org.eclipse.che.ide.ext.java.shared.dto.ReconcileResult; +import org.eclipse.che.jdt.javaeditor.JavaReconciler; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.internal.core.JavaModel; +import org.eclipse.jdt.internal.core.JavaModelManager; + +import static java.lang.String.format; + +/** + * Receives requests on reconcile operations from client side. + * + * @author Roman Nikitenko + */ +public class JavaReconcileRequestHandler { + private static final String INCOMING_METHOD = "request:java-reconcile"; + private static final JavaModel JAVA_MODEL = JavaModelManager.getJavaModelManager().getJavaModel(); + + @Inject + private JavaReconciler reconciler; + + @Inject + public void configureHandler(RequestHandlerConfigurator configurator) { + configurator.newConfiguration() + .methodName(INCOMING_METHOD) + .paramsAsDto(JavaClassInfo.class) + .resultAsDto(ReconcileResult.class) + .withFunction(this::getReconcileOperation); + } + + private ReconcileResult getReconcileOperation(JavaClassInfo javaClassInfo) { + IJavaProject javaProject = JAVA_MODEL.getJavaProject(javaClassInfo.getProjectPath()); + try { + return reconciler.reconcile(javaProject, javaClassInfo.getFQN()); + } catch (JavaModelException e) { + String error = format("Can't reconcile class: %s in project: %s, the reason is %s", + javaClassInfo.getFQN(), + javaProject.getPath().toOSString(), + e.getLocalizedMessage()); + throw new JsonRpcException(500, error); + } + } +} diff --git a/plugins/plugin-java/che-plugin-java-ext-lang-server/src/main/java/org/eclipse/che/plugin/java/server/inject/JdtGuiceModule.java b/plugins/plugin-java/che-plugin-java-ext-lang-server/src/main/java/org/eclipse/che/plugin/java/server/inject/JdtGuiceModule.java index fedc8f6436b..3f016ebe824 100644 --- a/plugins/plugin-java/che-plugin-java-ext-lang-server/src/main/java/org/eclipse/che/plugin/java/server/inject/JdtGuiceModule.java +++ b/plugins/plugin-java/che-plugin-java-ext-lang-server/src/main/java/org/eclipse/che/plugin/java/server/inject/JdtGuiceModule.java @@ -18,6 +18,7 @@ import org.eclipse.che.JavadocUrlProvider; import org.eclipse.che.inject.DynaModule; import org.eclipse.che.jdt.rest.UrlContextProvider; +import org.eclipse.che.plugin.java.server.JavaReconcileRequestHandler; import org.eclipse.che.plugin.java.server.ProjectListeners; import org.eclipse.che.plugin.java.server.refactoring.RefactoringManager; import org.eclipse.che.plugin.java.server.rest.CodeAssistService; @@ -57,6 +58,8 @@ protected void configure() { bind(RefactoringService.class); bind(SearchService.class); + bind(JavaReconcileRequestHandler.class).asEagerSingleton(); + bind(JavadocUrlProvider.class).to(JavadocUrlProviderImpl.class); requestStaticInjection(UrlContextProvider.class); @@ -75,6 +78,4 @@ protected String provideSettings(@Named("che.workspace.metadata") String wsMetad protected String provideIndex(@Named("che.workspace.metadata") String wsMetadata) { return Paths.get(System.getProperty("user.home"), wsMetadata, "index").toString(); } - - } diff --git a/plugins/plugin-java/che-plugin-java-ext-lang-server/src/test/java/org/eclipse/che/plugin/java/server/che/ReconcileTest.java b/plugins/plugin-java/che-plugin-java-ext-lang-server/src/test/java/org/eclipse/che/plugin/java/server/che/ReconcileTest.java index 947a6beb6f6..5265ccc5445 100644 --- a/plugins/plugin-java/che-plugin-java-ext-lang-server/src/test/java/org/eclipse/che/plugin/java/server/che/ReconcileTest.java +++ b/plugins/plugin-java/che-plugin-java-ext-lang-server/src/test/java/org/eclipse/che/plugin/java/server/che/ReconcileTest.java @@ -10,7 +10,9 @@ *******************************************************************************/ package org.eclipse.che.plugin.java.server.che; - +import org.eclipse.che.api.core.jsonrpc.commons.RequestTransmitter; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.project.server.EditorWorkingCopyManager; import org.eclipse.che.ide.ext.java.shared.dto.HighlightedPosition; import org.eclipse.che.ide.ext.java.shared.dto.ReconcileResult; import org.eclipse.che.jdt.javaeditor.JavaReconciler; @@ -34,6 +36,7 @@ import java.util.List; import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Mockito.mock; /** * @author Evgen Vidolob @@ -58,7 +61,10 @@ void setWorkingCopyContents(String contents) throws JavaModelException { @Before public void init() throws Exception { - reconciler = new JavaReconciler(new SemanticHighlightingReconciler()); + RequestTransmitter requestTransmitter = mock(RequestTransmitter.class); + EventService eventService = new EventService(); + EditorWorkingCopyManager editorWorkingCopyManager = new EditorWorkingCopyManager(null, eventService, requestTransmitter); + reconciler = new JavaReconciler(new SemanticHighlightingReconciler(), eventService, requestTransmitter, null, editorWorkingCopyManager); this.workingCopy = project.findType("p1.X").getCompilationUnit(); //.getWorkingCopy(this.wcOwner, null); } diff --git a/plugins/plugin-java/che-plugin-java-ext-lang-shared/src/main/java/org/eclipse/che/ide/ext/java/shared/dto/JavaClassInfo.java b/plugins/plugin-java/che-plugin-java-ext-lang-shared/src/main/java/org/eclipse/che/ide/ext/java/shared/dto/JavaClassInfo.java new file mode 100644 index 00000000000..8232c17165b --- /dev/null +++ b/plugins/plugin-java/che-plugin-java-ext-lang-shared/src/main/java/org/eclipse/che/ide/ext/java/shared/dto/JavaClassInfo.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * 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.ext.java.shared.dto; + +import org.eclipse.che.dto.shared.DTO; + +/** + * DTO represents the information about a java class. + * + * @author Roman Nikitenko + */ +@DTO +public interface JavaClassInfo { + + /** @return the FQN of java class */ + String getFQN(); + + JavaClassInfo withFQN(String fqn); + + /** @return the project path */ + String getProjectPath(); + + JavaClassInfo withProjectPath(String projectPath); +} diff --git a/plugins/plugin-java/che-plugin-java-ext-lang-shared/src/main/java/org/eclipse/che/ide/ext/java/shared/dto/Problem.java b/plugins/plugin-java/che-plugin-java-ext-lang-shared/src/main/java/org/eclipse/che/ide/ext/java/shared/dto/Problem.java index 3a871b1d61e..6988516d303 100644 --- a/plugins/plugin-java/che-plugin-java-ext-lang-shared/src/main/java/org/eclipse/che/ide/ext/java/shared/dto/Problem.java +++ b/plugins/plugin-java/che-plugin-java-ext-lang-shared/src/main/java/org/eclipse/che/ide/ext/java/shared/dto/Problem.java @@ -10,7 +10,6 @@ *******************************************************************************/ package org.eclipse.che.ide.ext.java.shared.dto; - import org.eclipse.che.dto.shared.DTO; import java.util.List; diff --git a/plugins/plugin-java/che-plugin-java-ext-lang-shared/src/main/java/org/eclipse/che/ide/ext/java/shared/dto/ReconcileResult.java b/plugins/plugin-java/che-plugin-java-ext-lang-shared/src/main/java/org/eclipse/che/ide/ext/java/shared/dto/ReconcileResult.java index 2a6bf23ad9d..3bf1a0fb0ba 100644 --- a/plugins/plugin-java/che-plugin-java-ext-lang-shared/src/main/java/org/eclipse/che/ide/ext/java/shared/dto/ReconcileResult.java +++ b/plugins/plugin-java/che-plugin-java-ext-lang-shared/src/main/java/org/eclipse/che/ide/ext/java/shared/dto/ReconcileResult.java @@ -10,7 +10,6 @@ *******************************************************************************/ package org.eclipse.che.ide.ext.java.shared.dto; - import org.eclipse.che.dto.shared.DTO; import java.util.List; @@ -25,8 +24,17 @@ public interface ReconcileResult { void setProblems(List problems); + ReconcileResult withProblems(List problems); + List getHighlightedPositions(); void setHighlightedPositions(List positions); + ReconcileResult withHighlightedPositions(List positions); + + String getFileLocation(); + + void setFileLocation(String path); + + ReconcileResult withFileLocation(String path); } diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/LanguageServerEditorConfiguration.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/LanguageServerEditorConfiguration.java index e531abc228f..d56379303cb 100644 --- a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/LanguageServerEditorConfiguration.java +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/LanguageServerEditorConfiguration.java @@ -21,8 +21,8 @@ import org.eclipse.che.ide.api.editor.partition.DocumentPartitioner; import org.eclipse.che.ide.api.editor.partition.DocumentPositionMap; import org.eclipse.che.ide.api.editor.quickfix.QuickAssistProcessor; +import org.eclipse.che.ide.api.editor.reconciler.DefaultReconciler; import org.eclipse.che.ide.api.editor.reconciler.Reconciler; -import org.eclipse.che.ide.api.editor.reconciler.ReconcilerWithAutoSave; import org.eclipse.che.ide.api.editor.signature.SignatureHelpProvider; import org.eclipse.che.ide.api.editor.texteditor.TextEditor; import org.eclipse.che.plugin.languageserver.ide.editor.quickassist.LanguageServerQuickAssistProcessor; @@ -42,7 +42,7 @@ public class LanguageServerEditorConfiguration extends DefaultTextEditorConfigur private final ServerCapabilities serverCapabilities; private final AnnotationModel annotationModel; - private final ReconcilerWithAutoSave reconciler; + private final DefaultReconciler reconciler; private final LanguageServerCodeassistProcessorFactory codeAssistProcessorFactory; private final SignatureHelpProvider signatureHelpProvider; private final LanguageServerFormatter formatter; @@ -72,7 +72,7 @@ public LanguageServerEditorConfiguration(@Assisted TextEditor editor, documentPositionMap.addPositionCategory(DocumentPositionMap.Categories.DEFAULT_CATEGORY); this.annotationModel = annotationModelFactory.get(documentPositionMap); - this.reconciler = new ReconcilerWithAutoSave(DocumentPartitioner.DEFAULT_CONTENT_TYPE, getPartitioner()); + this.reconciler = new DefaultReconciler(DocumentPartitioner.DEFAULT_CONTENT_TYPE, getPartitioner()); reconciler.addReconcilingStrategy(DocumentPartitioner.DEFAULT_CONTENT_TYPE, reconcileStrategyProviderFactory.build(serverCapabilities)); if (serverCapabilities.getSignatureHelpProvider() != null) { diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/LanguageServerEditorProvider.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/LanguageServerEditorProvider.java index 85e5e05050c..766f9cc9d5b 100644 --- a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/LanguageServerEditorProvider.java +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/LanguageServerEditorProvider.java @@ -20,7 +20,7 @@ import org.eclipse.che.ide.api.editor.EditorProvider; import org.eclipse.che.ide.api.editor.defaulteditor.AbstractTextEditorProvider; import org.eclipse.che.ide.api.editor.defaulteditor.EditorBuilder; -import org.eclipse.che.ide.api.editor.editorconfig.AutoSaveTextEditorConfiguration; +import org.eclipse.che.ide.api.editor.editorconfig.DefaultTextEditorConfiguration; import org.eclipse.che.ide.api.editor.texteditor.TextEditor; import org.eclipse.che.ide.api.resources.File; import org.eclipse.che.ide.api.resources.VirtualFile; @@ -68,7 +68,7 @@ public TextEditor getEditor() { } final TextEditor editor = editorBuilder.buildEditor(); - editor.initialize(new AutoSaveTextEditorConfiguration()); + editor.initialize(new DefaultTextEditorConfiguration()); return editor; } diff --git a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/LanguageServerFormatter.java b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/LanguageServerFormatter.java index ffa8b1c0bdd..5e0ca61b734 100644 --- a/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/LanguageServerFormatter.java +++ b/plugins/plugin-languageserver/che-plugin-languageserver-ide/src/main/java/org/eclipse/che/plugin/languageserver/ide/editor/LanguageServerFormatter.java @@ -28,8 +28,8 @@ import org.eclipse.che.ide.api.editor.texteditor.UndoableEditor; import org.eclipse.che.ide.api.notification.NotificationManager; import org.eclipse.che.ide.dto.DtoFactory; +import org.eclipse.che.ide.editor.preferences.EditorPreferencesManager; import org.eclipse.che.ide.editor.preferences.editorproperties.EditorProperties; -import org.eclipse.che.ide.editor.preferences.editorproperties.EditorPropertiesManager; import org.eclipse.che.ide.util.loging.Log; import org.eclipse.che.plugin.languageserver.ide.service.TextDocumentServiceClient; import org.eclipse.lsp4j.DocumentFormattingParams; @@ -54,7 +54,7 @@ public class LanguageServerFormatter implements ContentFormatter { private final DtoFactory dtoFactory; private final NotificationManager manager; private final ServerCapabilities capabilities; - private final EditorPropertiesManager editorPropertiesManager; + private final EditorPreferencesManager editorPreferencesManager; private TextEditor editor; @Inject @@ -62,12 +62,12 @@ public LanguageServerFormatter(TextDocumentServiceClient client, DtoFactory dtoFactory, NotificationManager manager, @Assisted ServerCapabilities capabilities, - EditorPropertiesManager editorPropertiesManager) { + EditorPreferencesManager editorPreferencesManager) { this.client = client; this.dtoFactory = dtoFactory; this.manager = manager; this.capabilities = capabilities; - this.editorPropertiesManager = editorPropertiesManager; + this.editorPreferencesManager = editorPreferencesManager; } @Override @@ -82,8 +82,6 @@ public void format(Document document) { //full document formatting formatFullDocument(document); } - - } @Override @@ -113,7 +111,6 @@ public void onDocumentChange(DocumentChangeEvent event) { Promise> promise = client.onTypeFormatting(params); handleFormatting(promise, document); - } } }); @@ -181,7 +178,7 @@ private FormattingOptions getFormattingOptions() { } private String getEditorProperty(EditorProperties property) { - return editorPropertiesManager.getEditorProperties().get(property.toString()).toString(); + return editorPreferencesManager.getEditorPreferences().get(property.toString()).toString(); } private void formatRange(TextRange selectedRange, Document document) { diff --git a/plugins/plugin-maven/che-plugin-maven-ide/pom.xml b/plugins/plugin-maven/che-plugin-maven-ide/pom.xml index 17100bf4d66..4c896d58f7b 100644 --- a/plugins/plugin-maven/che-plugin-maven-ide/pom.xml +++ b/plugins/plugin-maven/che-plugin-maven-ide/pom.xml @@ -52,6 +52,10 @@ javax.validation validation-api + + org.eclipse.che.core + che-core-api-core + org.eclipse.che.core che-core-api-factory-shared diff --git a/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/editor/PomEditorConfiguration.java b/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/editor/PomEditorConfiguration.java index a0960d1d3ea..f86f1c4b52a 100644 --- a/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/editor/PomEditorConfiguration.java +++ b/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/editor/PomEditorConfiguration.java @@ -15,7 +15,7 @@ import com.google.inject.assistedinject.Assisted; import org.eclipse.che.ide.api.editor.annotation.AnnotationModel; -import org.eclipse.che.ide.api.editor.editorconfig.AutoSaveTextEditorConfiguration; +import org.eclipse.che.ide.api.editor.editorconfig.DefaultTextEditorConfiguration; import org.eclipse.che.ide.api.editor.partition.DocumentPositionMap; import org.eclipse.che.ide.api.editor.reconciler.Reconciler; import org.eclipse.che.ide.editor.orion.client.OrionEditorPresenter; @@ -28,7 +28,7 @@ /** * @author Evgen Vidolob */ -public class PomEditorConfiguration extends AutoSaveTextEditorConfiguration { +public class PomEditorConfiguration extends DefaultTextEditorConfiguration { private AnnotationModel annotationModel; diff --git a/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/editor/PomReconcileUpdateOperation.java b/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/editor/PomReconcileUpdateOperation.java new file mode 100644 index 00000000000..19a887f9349 --- /dev/null +++ b/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/editor/PomReconcileUpdateOperation.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * 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.plugin.maven.client.editor; + +import com.google.web.bindery.event.shared.EventBus; + +import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerConfigurator; +import org.eclipse.che.api.project.shared.dto.ServerError; +import org.eclipse.che.ide.ext.java.client.editor.ReconcileOperationEvent; +import org.eclipse.che.ide.ext.java.shared.dto.ReconcileResult; +import org.eclipse.che.ide.util.loging.Log; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Receives result of reconcile operations from server side and notifies interested client consumers about it. + * + * @author Roman Nikitenko + */ +@Singleton +public class PomReconcileUpdateOperation { + private static final String POM_RECONCILE_ERROR_METHOD = "event:pom-reconcile-error"; + private static final String POM_RECONCILE_STATE_CHANGED_METHOD = "event:pom-reconcile-state-changed"; + + private final EventBus eventBus; + + @Inject + public PomReconcileUpdateOperation(EventBus eventBus) { + this.eventBus = eventBus; + } + + @Inject + public void configureHandler(RequestHandlerConfigurator configurator) { + configurator.newConfiguration() + .methodName(POM_RECONCILE_ERROR_METHOD) + .paramsAsDto(ServerError.class) + .noResult() + .withConsumer(this::onError); + + configurator.newConfiguration() + .methodName(POM_RECONCILE_STATE_CHANGED_METHOD) + .paramsAsDto(ReconcileResult.class) + .noResult() + .withConsumer(this::onSuccess); + } + + private void onSuccess(ReconcileResult reconcileResult) { + eventBus.fireEvent(new ReconcileOperationEvent(reconcileResult)); + } + + private void onError(ServerError reconcileError) { + Log.error(getClass(), reconcileError.getMessage()); + } +} diff --git a/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/editor/PomReconcilingStrategy.java b/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/editor/PomReconcilingStrategy.java index 1f8693293a9..fb54b303152 100644 --- a/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/editor/PomReconcilingStrategy.java +++ b/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/editor/PomReconcilingStrategy.java @@ -12,6 +12,8 @@ import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; +import com.google.web.bindery.event.shared.EventBus; +import com.google.web.bindery.event.shared.HandlerRegistration; import org.eclipse.che.api.promises.client.Operation; import org.eclipse.che.api.promises.client.OperationException; @@ -23,7 +25,10 @@ import org.eclipse.che.ide.api.editor.reconciler.ReconcilingStrategy; import org.eclipse.che.ide.api.editor.text.Region; import org.eclipse.che.ide.ext.java.client.editor.ProblemRequester; +import org.eclipse.che.ide.ext.java.client.editor.ReconcileOperationEvent; +import org.eclipse.che.ide.ext.java.client.editor.ReconcileOperationEvent.ReconcileOperationHandler; import org.eclipse.che.ide.ext.java.shared.dto.Problem; +import org.eclipse.che.ide.ext.java.shared.dto.ReconcileResult; import org.eclipse.che.ide.util.loging.Log; import org.eclipse.che.plugin.maven.client.service.MavenServerServiceClient; @@ -35,20 +40,24 @@ * * @author Evgen Vidolob */ -public class PomReconcilingStrategy implements ReconcilingStrategy { +public class PomReconcilingStrategy implements ReconcilingStrategy, ReconcileOperationHandler { private final AnnotationModel annotationModel; - private final EditorWithErrors editor; + private final EditorWithErrors editor; private final MavenServerServiceClient client; - private String pomPath; + + private String pomPath; + private HandlerRegistration reconcilerOperationHandlerRegistration; @Inject public PomReconcilingStrategy(@Assisted AnnotationModel annotationModel, @Assisted @NotNull final EditorWithErrors editor, - MavenServerServiceClient client) { + MavenServerServiceClient client, + EventBus eventBus) { this.annotationModel = annotationModel; this.editor = editor; this.client = client; + reconcilerOperationHandlerRegistration = eventBus.addHandler(ReconcileOperationEvent.TYPE, this); } @Override @@ -118,6 +127,14 @@ private void doReconcile(final List problems) { @Override public void closeReconciler() { + reconcilerOperationHandlerRegistration.removeHandler(); + } + @Override + public void onReconcileOperation(ReconcileResult reconcileResult) { + if (!pomPath.equals(reconcileResult.getFileLocation())) { + return; + } + doReconcile(reconcileResult.getProblems()); } } diff --git a/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/inject/MavenGinModule.java b/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/inject/MavenGinModule.java index 6cb738fc4d7..5878e3f64b4 100644 --- a/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/inject/MavenGinModule.java +++ b/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/inject/MavenGinModule.java @@ -21,6 +21,7 @@ import org.eclipse.che.ide.project.ResolvingProjectStateHolder; import org.eclipse.che.plugin.maven.client.command.MavenCommandType; import org.eclipse.che.plugin.maven.client.editor.PomEditorConfigurationFactory; +import org.eclipse.che.plugin.maven.client.editor.PomReconcileUpdateOperation; import org.eclipse.che.plugin.maven.client.editor.PomReconcilingStrategyFactory; import org.eclipse.che.plugin.maven.client.project.ResolvingMavenProjectStateHolder; import org.eclipse.che.plugin.maven.client.resource.MavenProjectInterceptor; @@ -51,5 +52,7 @@ protected void configure() { install(new GinFactoryModuleBuilder().build(PomEditorConfigurationFactory.class)); GinMultibinder.newSetBinder(binder(), ResolvingProjectStateHolder.class).addBinding().to(ResolvingMavenProjectStateHolder.class); + + bind(PomReconcileUpdateOperation.class).asEagerSingleton(); } } diff --git a/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/service/MavenServerServiceClientImpl.java b/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/service/MavenServerServiceClientImpl.java index 2cafcd49a7e..7764b23acb4 100644 --- a/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/service/MavenServerServiceClientImpl.java +++ b/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/service/MavenServerServiceClientImpl.java @@ -18,13 +18,11 @@ import org.eclipse.che.ide.api.app.AppContext; import org.eclipse.che.ide.commons.exception.UnmarshallerException; import org.eclipse.che.ide.ext.java.shared.dto.Problem; -import org.eclipse.che.ide.rest.AsyncRequestCallback; import org.eclipse.che.ide.rest.AsyncRequestFactory; import org.eclipse.che.ide.rest.DtoUnmarshallerFactory; import org.eclipse.che.ide.rest.StringUnmarshaller; import org.eclipse.che.ide.rest.Unmarshallable; import org.eclipse.che.ide.ui.loaders.request.LoaderFactory; -import org.eclipse.che.ide.util.loging.Log; import javax.validation.constraints.NotNull; import java.util.List; diff --git a/plugins/plugin-maven/che-plugin-maven-server/src/main/java/org/eclipse/che/plugin/maven/server/core/project/PomChangeListener.java b/plugins/plugin-maven/che-plugin-maven-server/src/main/java/org/eclipse/che/plugin/maven/server/core/project/PomChangeListener.java index 449d74c02be..c8e9d86f0e8 100644 --- a/plugins/plugin-maven/che-plugin-maven-server/src/main/java/org/eclipse/che/plugin/maven/server/core/project/PomChangeListener.java +++ b/plugins/plugin-maven/che-plugin-maven-server/src/main/java/org/eclipse/che/plugin/maven/server/core/project/PomChangeListener.java @@ -16,6 +16,8 @@ import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.core.notification.EventSubscriber; +import org.eclipse.che.api.project.server.EditorWorkingCopy; +import org.eclipse.che.api.project.server.EditorWorkingCopyManager; import org.eclipse.che.api.project.server.notification.ProjectItemModifiedEvent; import org.eclipse.che.api.project.shared.dto.event.PomModifiedEventDto; import org.eclipse.che.commons.schedule.executor.ThreadPullLauncher; @@ -43,6 +45,7 @@ public class PomChangeListener { private final MavenWorkspace mavenWorkspace; private final EclipseWorkspaceProvider eclipseWorkspaceProvider; + private final EditorWorkingCopyManager editorWorkingCopyManager; private final String workspacePath; private CopyOnWriteArraySet projectToUpdate = new CopyOnWriteArraySet<>(); @@ -50,10 +53,12 @@ public class PomChangeListener { public PomChangeListener(EventService eventService, MavenWorkspace mavenWorkspace, EclipseWorkspaceProvider eclipseWorkspaceProvider, + EditorWorkingCopyManager editorWorkingCopyManager, ThreadPullLauncher launcher, @Named("che.user.workspaces.storage") String workspacePath) { this.mavenWorkspace = mavenWorkspace; this.eclipseWorkspaceProvider = eclipseWorkspaceProvider; + this.editorWorkingCopyManager = editorWorkingCopyManager; this.workspacePath = workspacePath; launcher.scheduleWithFixedDelay(this::updateProms, 20, 3, TimeUnit.SECONDS); @@ -86,7 +91,12 @@ public void onEvent(PomModifiedEventDto event) { private boolean pomIsValid(String path) { try { - Model.readFrom(new File(workspacePath, path)); + EditorWorkingCopy workingCopy = editorWorkingCopyManager.getWorkingCopy(path); + if (workingCopy != null) { + Model.readFrom(workingCopy.getContent()); + } else { + Model.readFrom(new File(workspacePath, path)); + } } catch (Exception e) { JavaPlugin.log(e); return false; diff --git a/plugins/plugin-maven/che-plugin-maven-server/src/main/java/org/eclipse/che/plugin/maven/server/core/reconcile/PomReconciler.java b/plugins/plugin-maven/che-plugin-maven-server/src/main/java/org/eclipse/che/plugin/maven/server/core/reconcile/PomReconciler.java new file mode 100644 index 00000000000..6ec510c3694 --- /dev/null +++ b/plugins/plugin-maven/che-plugin-maven-server/src/main/java/org/eclipse/che/plugin/maven/server/core/reconcile/PomReconciler.java @@ -0,0 +1,245 @@ +/******************************************************************************* + * 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.plugin.maven.server.core.reconcile; + +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import org.eclipse.che.api.core.ForbiddenException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.jsonrpc.commons.RequestTransmitter; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.core.notification.EventSubscriber; +import org.eclipse.che.api.project.server.EditorWorkingCopy; +import org.eclipse.che.api.project.server.EditorWorkingCopyManager; +import org.eclipse.che.api.project.server.EditorWorkingCopyUpdatedEvent; +import org.eclipse.che.api.project.server.ProjectManager; +import org.eclipse.che.api.project.server.VirtualFileEntry; +import org.eclipse.che.api.project.shared.dto.EditorChangesDto; +import org.eclipse.che.api.project.shared.dto.ServerError; +import org.eclipse.che.commons.xml.XMLTreeException; +import org.eclipse.che.dto.server.DtoFactory; +import org.eclipse.che.ide.ext.java.shared.dto.Problem; +import org.eclipse.che.ide.ext.java.shared.dto.ReconcileResult; +import org.eclipse.che.ide.maven.tools.Model; +import org.eclipse.che.maven.data.MavenProjectProblem; +import org.eclipse.che.plugin.maven.server.core.MavenProjectManager; +import org.eclipse.che.plugin.maven.server.core.project.MavenProject; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.Path; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.Document; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.SAXParseException; + +import javax.annotation.PreDestroy; +import javax.inject.Provider; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import static com.google.common.base.Strings.isNullOrEmpty; +import static java.lang.String.format; +import static java.nio.charset.Charset.defaultCharset; +import static org.eclipse.che.maven.data.MavenConstants.POM_FILE_NAME; + +/** + * Handles reconcile operations for pom.xml file. + * + * @author Roman Nikitenko + */ +@Singleton +public class PomReconciler { + private static final Logger LOG = LoggerFactory.getLogger(PomReconciler.class); + private static final String RECONCILE_ERROR_METHOD = "event:pom-reconcile-error"; + private static final String RECONCILE_STATE_CHANGED_METHOD = "event:pom-reconcile-state-changed"; + + private Provider projectManagerProvider; + private MavenProjectManager mavenProjectManager; + private EditorWorkingCopyManager editorWorkingCopyManager; + private EventService eventService; + private RequestTransmitter transmitter; + private EventSubscriber editorContentUpdateEventSubscriber; + + @Inject + public PomReconciler(Provider projectManagerProvider, + MavenProjectManager mavenProjectManager, + EditorWorkingCopyManager editorWorkingCopyManager, + EventService eventService, + RequestTransmitter transmitter) { + this.projectManagerProvider = projectManagerProvider; + this.mavenProjectManager = mavenProjectManager; + this.editorWorkingCopyManager = editorWorkingCopyManager; + this.eventService = eventService; + this.transmitter = transmitter; + + editorContentUpdateEventSubscriber = new EventSubscriber() { + @Override + public void onEvent(EditorWorkingCopyUpdatedEvent event) { + onEditorContentUpdated(event); + } + }; + eventService.subscribe(editorContentUpdateEventSubscriber); + } + + @PreDestroy + private void unsubscribe() { + eventService.unsubscribe(editorContentUpdateEventSubscriber); + } + + /** + * Handles reconcile operations for pom.xml file by given path. + * + * @param pomPath + * path to the pom file to reconcile + * @return result of reconcile operation as a list of {@link Problem}s + * @throws NotFoundException + * if file is not found by given {@code pomPath} + * @throws ForbiddenException + * if item is not a file + * @throws ServerException + * if other error occurs + */ + public List reconcile(String pomPath) throws ServerException, ForbiddenException, NotFoundException { + VirtualFileEntry entry = projectManagerProvider.get().getProjectsRoot().getChild(pomPath); + if (entry == null) { + throw new NotFoundException(format("File '%s' doesn't exist", pomPath)); + } + + EditorWorkingCopy workingCopy = editorWorkingCopyManager.getWorkingCopy(pomPath); + String pomContent = workingCopy != null ? workingCopy.getContentAsString() : entry.getVirtualFile().getContentAsString(); + String projectPath = entry.getPath().getParent().toString(); + + return reconcile(pomPath, projectPath, pomContent); + } + + private List reconcile(String pomPath, String projectPath, String pomContent) throws ServerException, NotFoundException { + List result = new ArrayList<>(); + + if (isNullOrEmpty(pomContent)) { + throw new ServerException(format("Couldn't reconcile pom file '%s' because its content is empty", pomPath)); + } + + try { + Model.readFrom(new ByteArrayInputStream(pomContent.getBytes(defaultCharset()))); + + IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectPath); + MavenProject mavenProject = mavenProjectManager.findMavenProject(project); + if (mavenProject == null) { + return result; + } + + List problems = mavenProject.getProblems(); + + int start = pomContent.indexOf(" problemList = problems.stream().map(mavenProjectProblem -> DtoFactory.newDto(Problem.class) + .withError(true) + .withSourceStart(start) + .withSourceEnd(end) + .withMessage(mavenProjectProblem + .getDescription())) + .collect(Collectors.toList()); + result.addAll(problemList); + } catch (XMLTreeException exception) { + Throwable cause = exception.getCause(); + if (cause != null && cause instanceof SAXParseException) { + result.add(createProblem(pomContent, (SAXParseException)cause)); + + } else { + String error = format("Couldn't reconcile pom file '%s', the reason is '%s'", pomPath, exception.getLocalizedMessage()); + LOG.error(error, exception); + throw new ServerException(error); + } + } catch (IOException e) { + String error = format("Couldn't reconcile pom file '%s', the reason is '%s'", pomPath, e.getLocalizedMessage()); + LOG.error(error, e); + throw new ServerException(error); + } + return result; + } + + private void onEditorContentUpdated(EditorWorkingCopyUpdatedEvent event) { + EditorChangesDto editorChanges = event.getChanges(); + String endpointId = event.getEndpointId(); + String fileLocation = editorChanges.getFileLocation(); + String fileName = new Path(fileLocation).lastSegment(); + if (!POM_FILE_NAME.equals(fileName)) { + return; + } + + try { + EditorWorkingCopy workingCopy = editorWorkingCopyManager.getWorkingCopy(fileLocation); + if (workingCopy == null) { + return; + } + + String newPomContent = workingCopy.getContentAsString(); + if (isNullOrEmpty(newPomContent)) { + return; + } + + String projectPath = editorChanges.getProjectPath(); + List problemList = reconcile(fileLocation, projectPath, newPomContent); + DtoFactory dtoFactory = DtoFactory.getInstance(); + ReconcileResult reconcileResult = dtoFactory.createDto(ReconcileResult.class) + .withFileLocation(fileLocation) + .withProblems(problemList); + transmitter.newRequest() + .endpointId(endpointId) + .methodName(RECONCILE_STATE_CHANGED_METHOD) + .paramsAsDto(reconcileResult) + .sendAndSkipResult(); + } catch (Exception e) { + String error = e.getLocalizedMessage(); + LOG.error(error, e); + transmitError(500, error, endpointId); + } + } + + private void transmitError(int code, String errorMessage, String endpointId) { + DtoFactory dtoFactory = DtoFactory.getInstance(); + ServerError reconcileError = dtoFactory.createDto(ServerError.class) + .withCode(code) + .withMessage(errorMessage); + transmitter.newRequest() + .endpointId(endpointId) + .methodName(RECONCILE_ERROR_METHOD) + .paramsAsDto(reconcileError) + .sendAndSkipResult(); + } + + private Problem createProblem(String pomContent, SAXParseException spe) { + Problem problem = DtoFactory.newDto(Problem.class); + problem.setError(true); + problem.setMessage(spe.getMessage()); + if (pomContent != null) { + int lineNumber = spe.getLineNumber(); + int columnNumber = spe.getColumnNumber(); + try { + Document document = new Document(pomContent); + int lineOffset = document.getLineOffset(lineNumber - 1); + problem.setSourceStart(lineOffset + columnNumber - 1); + problem.setSourceEnd(lineOffset + columnNumber); + } catch (BadLocationException e) { + LOG.error(e.getMessage(), e); + } + + } + return problem; + } +} diff --git a/plugins/plugin-maven/che-plugin-maven-server/src/main/java/org/eclipse/che/plugin/maven/server/rest/MavenServerService.java b/plugins/plugin-maven/che-plugin-maven-server/src/main/java/org/eclipse/che/plugin/maven/server/rest/MavenServerService.java index d3d6de0dedf..40fefcdf47e 100644 --- a/plugins/plugin-maven/che-plugin-maven-server/src/main/java/org/eclipse/che/plugin/maven/server/rest/MavenServerService.java +++ b/plugins/plugin-maven/che-plugin-maven-server/src/main/java/org/eclipse/che/plugin/maven/server/rest/MavenServerService.java @@ -17,18 +17,14 @@ import com.google.inject.Inject; +import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; -import org.eclipse.che.api.project.server.ProjectManager; import org.eclipse.che.api.project.server.ProjectRegistry; import org.eclipse.che.api.project.server.RegisteredProject; import org.eclipse.che.api.project.server.VirtualFileEntry; -import org.eclipse.che.commons.xml.XMLTreeException; -import org.eclipse.che.dto.server.DtoFactory; import org.eclipse.che.ide.ext.java.shared.dto.Problem; -import org.eclipse.che.ide.maven.tools.Model; -import org.eclipse.che.maven.data.MavenProjectProblem; import org.eclipse.che.maven.server.MavenTerminal; import org.eclipse.che.plugin.maven.server.MavenServerWrapper; import org.eclipse.che.plugin.maven.server.MavenWrapperManager; @@ -37,15 +33,9 @@ import org.eclipse.che.plugin.maven.server.core.MavenProjectManager; import org.eclipse.che.plugin.maven.server.core.MavenWorkspace; import org.eclipse.che.plugin.maven.server.core.classpath.ClasspathManager; -import org.eclipse.che.plugin.maven.server.core.project.MavenProject; +import org.eclipse.che.plugin.maven.server.core.reconcile.PomReconciler; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IWorkspace; -import org.eclipse.core.resources.ResourcesPlugin; -import org.eclipse.jface.text.BadLocationException; -import org.eclipse.jface.text.Document; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.xml.sax.SAXParseException; import javax.ws.rs.GET; import javax.ws.rs.POST; @@ -53,8 +43,6 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Response; -import java.io.IOException; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -68,14 +56,10 @@ */ @Path("/maven/server") public class MavenServerService { - private static final Logger LOG = LoggerFactory.getLogger(MavenServerService.class); - private final MavenWrapperManager wrapperManager; private final ProjectRegistry projectRegistry; - private final MavenProjectManager mavenProjectManager; private final MavenWorkspace mavenWorkspace; private final EclipseWorkspaceProvider eclipseWorkspaceProvider; - private final ProjectManager cheProjectManager; @Inject private MavenProgressNotifier notifier; @@ -89,18 +73,17 @@ public class MavenServerService { @Inject private ClasspathManager classpathManager; + @Inject + private PomReconciler pomReconciler; + @Inject public MavenServerService(MavenWrapperManager wrapperManager, ProjectRegistry projectRegistry, - ProjectManager projectManager, - MavenProjectManager mavenProjectManager, MavenWorkspace mavenWorkspace, EclipseWorkspaceProvider eclipseWorkspaceProvider) { - cheProjectManager = projectManager; this.wrapperManager = wrapperManager; this.projectRegistry = projectRegistry; - this.mavenProjectManager = mavenProjectManager; this.mavenWorkspace = mavenWorkspace; this.eclipseWorkspaceProvider = eclipseWorkspaceProvider; } @@ -171,65 +154,8 @@ public Response reimportDependencies(@ApiParam(value = "The paths to projects wh @ApiResponses({@ApiResponse(code = 200, message = "OK")}) @Produces("application/json") public List reconcilePom(@ApiParam(value = "The paths to pom.xml file which need to be reconciled") - @QueryParam("pompath") String pomPath) { - VirtualFileEntry entry = null; - List result = new ArrayList<>(); - try { - entry = cheProjectManager.getProjectsRoot().getChild(pomPath); - if (entry == null) { - return result; - } - - Model.readFrom(entry.getVirtualFile()); - org.eclipse.che.api.vfs.Path path = entry.getPath(); - String pomContent = entry.getVirtualFile().getContentAsString(); - MavenProject mavenProject = - mavenProjectManager.findMavenProject(ResourcesPlugin.getWorkspace().getRoot().getProject(path.getParent().toString())); - if (mavenProject == null) { - return result; - } - - List problems = mavenProject.getProblems(); - int start = pomContent.indexOf(" problemList = problems.stream().map(mavenProjectProblem -> DtoFactory.newDto(Problem.class) - .withError(true) - .withSourceStart(start) - .withSourceEnd(end) - .withMessage(mavenProjectProblem.getDescription())) - .collect(Collectors.toList()); - result.addAll(problemList); - } catch (ServerException | ForbiddenException | IOException e) { - LOG.error(e.getMessage(), e); - } catch (XMLTreeException exception) { - Throwable cause = exception.getCause(); - if (cause != null && cause instanceof SAXParseException) { - result.add(createProblem(entry, (SAXParseException)cause)); - } - } - return result; - } - - private Problem createProblem(VirtualFileEntry entry, SAXParseException spe) { - Problem problem = DtoFactory.newDto(Problem.class); - problem.setError(true); - problem.setMessage(spe.getMessage()); - if (entry != null) { - int lineNumber = spe.getLineNumber(); - int columnNumber = spe.getColumnNumber(); - try { - String content = entry.getVirtualFile().getContentAsString(); - Document document = new Document(content); - int lineOffset = document.getLineOffset(lineNumber - 1); - problem.setSourceStart(lineOffset + columnNumber - 1); - problem.setSourceEnd(lineOffset + columnNumber); - } catch (ForbiddenException | ServerException | BadLocationException e) { - LOG.error(e.getMessage(), e); - } - - } - return problem; + @QueryParam("pompath") String pomPath) + throws ForbiddenException, ConflictException, NotFoundException, ServerException { + return pomReconciler.reconcile(pomPath); } - } diff --git a/plugins/plugin-maven/che-plugin-maven-server/src/test/java/org/eclipse/che/plugin/maven/server/PomReconcilerTest.java b/plugins/plugin-maven/che-plugin-maven-server/src/test/java/org/eclipse/che/plugin/maven/server/core/reconcile/PomReconcilerTest.java similarity index 80% rename from plugins/plugin-maven/che-plugin-maven-server/src/test/java/org/eclipse/che/plugin/maven/server/PomReconcilerTest.java rename to plugins/plugin-maven/che-plugin-maven-server/src/test/java/org/eclipse/che/plugin/maven/server/core/reconcile/PomReconcilerTest.java index c7ba68fdecc..8558562272b 100644 --- a/plugins/plugin-maven/che-plugin-maven-server/src/test/java/org/eclipse/che/plugin/maven/server/PomReconcilerTest.java +++ b/plugins/plugin-maven/che-plugin-maven-server/src/test/java/org/eclipse/che/plugin/maven/server/core/reconcile/PomReconcilerTest.java @@ -8,16 +8,21 @@ * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ -package org.eclipse.che.plugin.maven.server; +package org.eclipse.che.plugin.maven.server.core.reconcile; import com.google.gson.JsonObject; import com.google.inject.Provider; +import org.eclipse.che.api.core.jsonrpc.commons.RequestTransmitter; +import org.eclipse.che.api.project.server.EditorWorkingCopyManager; import org.eclipse.che.api.project.server.FolderEntry; +import org.eclipse.che.api.project.server.ProjectManager; import org.eclipse.che.api.project.server.ProjectRegistry; import org.eclipse.che.api.project.server.VirtualFileEntry; import org.eclipse.che.ide.ext.java.shared.dto.Problem; import org.eclipse.che.maven.server.MavenTerminal; +import org.eclipse.che.plugin.maven.server.BaseTest; +import org.eclipse.che.plugin.maven.server.MavenWrapperManager; import org.eclipse.che.plugin.maven.server.core.EclipseWorkspaceProvider; import org.eclipse.che.plugin.maven.server.core.MavenCommunication; import org.eclipse.che.plugin.maven.server.core.MavenExecutorService; @@ -25,8 +30,6 @@ import org.eclipse.che.plugin.maven.server.core.MavenWorkspace; import org.eclipse.che.plugin.maven.server.core.classpath.ClasspathManager; import org.eclipse.che.plugin.maven.server.core.project.MavenProject; -import org.eclipse.che.plugin.maven.server.projecttype.MavenValueProvider; -import org.eclipse.che.plugin.maven.server.rest.MavenServerService; import org.eclipse.che.plugin.maven.server.rmi.MavenServerManagerTest; import org.eclipse.che.plugin.maven.shared.MessageType; import org.eclipse.che.plugin.maven.shared.dto.NotificationMessage; @@ -41,6 +44,7 @@ import java.util.List; import java.util.Set; +import static java.lang.String.format; import static org.fest.assertions.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -50,15 +54,17 @@ */ public class PomReconcilerTest extends BaseTest { - - private MavenProjectManager projectManager; + private MavenProjectManager mavenProjectManager; private MavenWorkspace mavenWorkspace; - private MavenValueProvider mavenValueProvider; + private PomReconciler pomReconciler; @BeforeMethod public void setUp() throws Exception { Provider projectRegistryProvider = (Provider)mock(Provider.class); when(projectRegistryProvider.get()).thenReturn(projectRegistry); + + RequestTransmitter requestTransmitter = mock(RequestTransmitter.class); + MavenServerManagerTest.MyMavenServerProgressNotifier mavenNotifier = new MavenServerManagerTest.MyMavenServerProgressNotifier(); MavenTerminal terminal = new MavenTerminal() { @Override @@ -75,15 +81,15 @@ public void print(int level, String message, Throwable throwable) throws RemoteE mavenServerManager.setLocalRepository(localRepository); MavenWrapperManager wrapperManager = new MavenWrapperManager(mavenServerManager); - projectManager = + mavenProjectManager = new MavenProjectManager(wrapperManager, mavenServerManager, terminal, mavenNotifier, new EclipseWorkspaceProvider()); - - + Provider projectManagerProvider = (Provider)mock(Provider.class); + when(projectManagerProvider.get()).thenReturn(pm); ClasspathManager classpathManager = - new ClasspathManager(root.getAbsolutePath(), wrapperManager, projectManager, terminal, mavenNotifier); + new ClasspathManager(root.getAbsolutePath(), wrapperManager, mavenProjectManager, terminal, mavenNotifier); - mavenWorkspace = new MavenWorkspace(projectManager, mavenNotifier, new MavenExecutorService(), projectRegistryProvider, + mavenWorkspace = new MavenWorkspace(mavenProjectManager, mavenNotifier, new MavenExecutorService(), projectRegistryProvider, new MavenCommunication() { @Override public void sendUpdateMassage(Set updated, List removed) { @@ -100,18 +106,20 @@ public void send(JsonObject object, MessageType type) { } }, classpathManager, eventService, new EclipseWorkspaceProvider()); - + EditorWorkingCopyManager editorWorkingCopyManager = + new EditorWorkingCopyManager(projectManagerProvider, eventService, requestTransmitter); + pomReconciler = + new PomReconciler(projectManagerProvider, mavenProjectManager, editorWorkingCopyManager, eventService, requestTransmitter); } @Test public void testProblemPosition() throws Exception { - MavenServerService serverService = new MavenServerService(null, projectRegistry, pm, projectManager, null, null); FolderEntry testProject = createTestProject("A", ""); VirtualFileEntry child = testProject.getChild("pom.xml"); String newContent = getPomContent(" problems = serverService.reconcilePom("/A/pom.xml"); + List problems = pomReconciler.reconcile("/A/pom.xml"); assertThat(problems).isNotEmpty(); Problem problem = problems.get(0); @@ -122,11 +130,10 @@ public void testProblemPosition() throws Exception { @Test public void testReconcilePomWhenMavenProjectIsNotFound() throws Exception { - MavenServerService serverService = new MavenServerService(null, projectRegistry, pm, projectManager, null, null); FolderEntry testProject = createTestProject(PROJECT_NAME, ""); VirtualFileEntry pom = testProject.getChild("pom.xml"); - List problems = serverService.reconcilePom(String.format("/%s/pom.xml", PROJECT_NAME)); + List problems = pomReconciler.reconcile(format("/%s/pom.xml", PROJECT_NAME)); assertThat(problems).isEmpty(); assertThat(pom).isNotNull(); @@ -140,14 +147,13 @@ public void testReconcilePomWhenPomContainsCorrectDependency() throws Exception " 3.8.1\n" + " test\n" + " \n"; - MavenServerService serverService = new MavenServerService(null, projectRegistry, pm, projectManager, null, null); FolderEntry testProject = createTestProject(PROJECT_NAME, getPomContentWithDependency(dependency)); VirtualFileEntry pom = testProject.getChild("pom.xml"); IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(PROJECT_NAME); mavenWorkspace.update(Collections.singletonList(project)); mavenWorkspace.waitForUpdate(); - List problems = serverService.reconcilePom(String.format("/%s/pom.xml", PROJECT_NAME)); + List problems = pomReconciler.reconcile(format("/%s/pom.xml", PROJECT_NAME)); assertThat(problems).isEmpty(); assertThat(pom).isNotNull(); @@ -161,14 +167,13 @@ public void testReconcilePomWhenPomContainsDependecyWithIncorrectVersion() throw " 33333333.8.1\n" + " test\n" + " \n"; - MavenServerService serverService = new MavenServerService(null, projectRegistry, pm, projectManager, null, null); FolderEntry testProject = createTestProject(PROJECT_NAME, getPomContentWithDependency(brokenDependency)); VirtualFileEntry pom = testProject.getChild("pom.xml"); IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(PROJECT_NAME); mavenWorkspace.update(Collections.singletonList(project)); mavenWorkspace.waitForUpdate(); - List problems = serverService.reconcilePom(String.format("/%s/pom.xml", PROJECT_NAME)); + List problems = pomReconciler.reconcile(format("/%s/pom.xml", PROJECT_NAME)); assertThat(problems).hasSize(1); assertThat(problems.get(0).isError()).isTrue(); @@ -183,14 +188,13 @@ public void testReconcilePomWhenPomContainsDependecyWithIncorrectGroupId() throw " 3.8.1\n" + " test\n" + " \n"; - MavenServerService serverService = new MavenServerService(null, projectRegistry, pm, projectManager, null, null); FolderEntry testProject = createTestProject(PROJECT_NAME, getPomContentWithDependency(brokenDependency)); VirtualFileEntry pom = testProject.getChild("pom.xml"); IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(PROJECT_NAME); mavenWorkspace.update(Collections.singletonList(project)); mavenWorkspace.waitForUpdate(); - List problems = serverService.reconcilePom(String.format("/%s/pom.xml", PROJECT_NAME)); + List problems = pomReconciler.reconcile(format("/%s/pom.xml", PROJECT_NAME)); assertThat(problems).hasSize(1); assertThat(problems.get(0).isError()).isTrue(); @@ -205,14 +209,13 @@ public void testReconcilePomWhenPomContainsDependecyWithIncorrectAtrifactId() th " 3.8.1\n" + " test\n" + " \n"; - MavenServerService serverService = new MavenServerService(null, projectRegistry, pm, projectManager, null, null); FolderEntry testProject = createTestProject(PROJECT_NAME, getPomContentWithDependency(brokenDependency)); VirtualFileEntry pom = testProject.getChild("pom.xml"); IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(PROJECT_NAME); mavenWorkspace.update(Collections.singletonList(project)); mavenWorkspace.waitForUpdate(); - List problems = serverService.reconcilePom(String.format("/%s/pom.xml", PROJECT_NAME)); + List problems = pomReconciler.reconcile(format("/%s/pom.xml", PROJECT_NAME)); assertThat(problems).hasSize(1); assertThat(problems.get(0).isError()).isTrue(); @@ -220,13 +223,13 @@ public void testReconcilePomWhenPomContainsDependecyWithIncorrectAtrifactId() th } private String getPomContentWithDependency(String dependency) { - return String.format("org.eclipse.che.examples\n" + - "web-java-spring\n" + - "war\n" + - "1.0-SNAPSHOT\n" + - "SpringDemo" + - "\n" + - "%s" + - "", dependency); + return format("org.eclipse.che.examples\n" + + "web-java-spring\n" + + "war\n" + + "1.0-SNAPSHOT\n" + + "SpringDemo" + + "\n" + + "%s" + + "", dependency); } } diff --git a/plugins/plugin-web/che-plugin-web-ext-web/src/main/java/org/eclipse/che/ide/ext/web/html/editor/HtmlEditorConfiguration.java b/plugins/plugin-web/che-plugin-web-ext-web/src/main/java/org/eclipse/che/ide/ext/web/html/editor/HtmlEditorConfiguration.java index f80c5877f31..6234a3f393e 100644 --- a/plugins/plugin-web/che-plugin-web-ext-web/src/main/java/org/eclipse/che/ide/ext/web/html/editor/HtmlEditorConfiguration.java +++ b/plugins/plugin-web/che-plugin-web-ext-web/src/main/java/org/eclipse/che/ide/ext/web/html/editor/HtmlEditorConfiguration.java @@ -13,7 +13,7 @@ import org.eclipse.che.ide.api.editor.changeintercept.ChangeInterceptorProvider; import org.eclipse.che.ide.api.editor.changeintercept.TextChangeInterceptor; import org.eclipse.che.ide.api.editor.codeassist.CodeAssistProcessor; -import org.eclipse.che.ide.api.editor.editorconfig.AutoSaveTextEditorConfiguration; +import org.eclipse.che.ide.api.editor.editorconfig.DefaultTextEditorConfiguration; import org.eclipse.che.ide.api.editor.partition.DocumentPartitioner; import java.util.ArrayList; @@ -25,7 +25,7 @@ /** * The html type editor configuration. */ -public class HtmlEditorConfiguration extends AutoSaveTextEditorConfiguration { +public class HtmlEditorConfiguration extends DefaultTextEditorConfiguration { /** * Auto edit factories. diff --git a/plugins/plugin-web/che-plugin-web-ext-web/src/main/java/org/eclipse/che/ide/ext/web/js/editor/JsEditorConfiguration.java b/plugins/plugin-web/che-plugin-web-ext-web/src/main/java/org/eclipse/che/ide/ext/web/js/editor/JsEditorConfiguration.java index 99a0b51fd62..5e15ca234c0 100644 --- a/plugins/plugin-web/che-plugin-web-ext-web/src/main/java/org/eclipse/che/ide/ext/web/js/editor/JsEditorConfiguration.java +++ b/plugins/plugin-web/che-plugin-web-ext-web/src/main/java/org/eclipse/che/ide/ext/web/js/editor/JsEditorConfiguration.java @@ -10,12 +10,12 @@ *******************************************************************************/ package org.eclipse.che.ide.ext.web.js.editor; -import org.eclipse.che.ide.ext.web.html.editor.AutoEditStrategyFactory; import org.eclipse.che.ide.api.editor.changeintercept.ChangeInterceptorProvider; import org.eclipse.che.ide.api.editor.changeintercept.TextChangeInterceptor; import org.eclipse.che.ide.api.editor.codeassist.CodeAssistProcessor; -import org.eclipse.che.ide.api.editor.editorconfig.AutoSaveTextEditorConfiguration; +import org.eclipse.che.ide.api.editor.editorconfig.DefaultTextEditorConfiguration; import org.eclipse.che.ide.api.editor.partition.DocumentPartitioner; +import org.eclipse.che.ide.ext.web.html.editor.AutoEditStrategyFactory; import java.util.ArrayList; import java.util.HashMap; @@ -26,9 +26,9 @@ /** * The JS type editor configuration. * - * @author Evgen Vidolob + * @author Evgen Vidolob */ -public class JsEditorConfiguration extends AutoSaveTextEditorConfiguration { +public class JsEditorConfiguration extends DefaultTextEditorConfiguration { private Set autoEditStrategyFactories; private DefaultCodeAssistProcessor defaultProcessor; diff --git a/samples/sample-plugin-json/che-sample-plugin-json-ide/src/main/java/org/eclipse/che/plugin/jsonexample/ide/editor/JsonExampleEditorProvider.java b/samples/sample-plugin-json/che-sample-plugin-json-ide/src/main/java/org/eclipse/che/plugin/jsonexample/ide/editor/JsonExampleEditorProvider.java index ae1feb6264e..482e4b36003 100644 --- a/samples/sample-plugin-json/che-sample-plugin-json-ide/src/main/java/org/eclipse/che/plugin/jsonexample/ide/editor/JsonExampleEditorProvider.java +++ b/samples/sample-plugin-json/che-sample-plugin-json-ide/src/main/java/org/eclipse/che/plugin/jsonexample/ide/editor/JsonExampleEditorProvider.java @@ -10,12 +10,9 @@ *******************************************************************************/ package org.eclipse.che.plugin.jsonexample.ide.editor; -import org.eclipse.che.ide.api.editor.EditorPartPresenter; import org.eclipse.che.ide.api.editor.EditorProvider; import org.eclipse.che.ide.api.editor.defaulteditor.AbstractTextEditorProvider; -import org.eclipse.che.ide.api.editor.defaulteditor.DefaultTextEditorProvider; import org.eclipse.che.ide.api.editor.editorconfig.TextEditorConfiguration; -import org.eclipse.che.ide.api.editor.texteditor.TextEditor; import javax.inject.Inject; diff --git a/wsagent/che-core-api-project-shared/src/main/java/org/eclipse/che/api/project/shared/dto/EditorChangesDto.java b/wsagent/che-core-api-project-shared/src/main/java/org/eclipse/che/api/project/shared/dto/EditorChangesDto.java new file mode 100644 index 00000000000..3dc7ca16b92 --- /dev/null +++ b/wsagent/che-core-api-project-shared/src/main/java/org/eclipse/che/api/project/shared/dto/EditorChangesDto.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * 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.api.project.shared.dto; + +import org.eclipse.che.dto.shared.DTO; + +/** + * DTO represents the information about a text change of editor content. + * + * @author Roman Nikitenko + */ +@DTO +public interface EditorChangesDto { + /** Returns the full path to the file that was changed */ + String getFileLocation(); + + EditorChangesDto withFileLocation(String fileLocation); + + /** Returns the path to the project that contains the modified file */ + String getProjectPath(); + + EditorChangesDto withProjectPath(String path); + + Type getType(); + + EditorChangesDto withType(Type type); + + /** Returns the offset of the change. */ + int getOffset(); + + EditorChangesDto withOffset(int offset); + + /** Returns length of the text change. */ + int getLength(); + + EditorChangesDto withLength(int length); + + /** Returns text of the change. */ + String getText(); + + EditorChangesDto withText(String text); + + /** Returns the number of characters removed from the file. */ + int getRemovedCharCount(); + + EditorChangesDto withRemovedCharCount(int removedCharCount); + + enum Type { + /** Identifies an insert operation. */ + INSERT, + /** Identifies an remove operation. */ + REMOVE, + } +} diff --git a/wsagent/che-core-api-project-shared/src/main/java/org/eclipse/che/api/project/shared/dto/ServerError.java b/wsagent/che-core-api-project-shared/src/main/java/org/eclipse/che/api/project/shared/dto/ServerError.java new file mode 100644 index 00000000000..788a5d6cac7 --- /dev/null +++ b/wsagent/che-core-api-project-shared/src/main/java/org/eclipse/che/api/project/shared/dto/ServerError.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * 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.api.project.shared.dto; + +import org.eclipse.che.dto.shared.DTO; + +/** + * DTO represents the information about an internal server error. + * + * @author Roman Nikitenko + */ +@DTO +public interface ServerError { + int getCode(); + + ServerError withCode(int errorCode); + + String getMessage(); + + ServerError withMessage(String errorMessage); +} diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/EditorChangesTracker.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/EditorChangesTracker.java new file mode 100644 index 00000000000..af0762bee54 --- /dev/null +++ b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/EditorChangesTracker.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * 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.api.project.server; + +import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerConfigurator; +import org.eclipse.che.api.project.shared.dto.EditorChangesDto; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Receives notifications about editor changes from client side. + * + * @author Roman Nikitenko + */ +@Singleton +public class EditorChangesTracker { + private static final String INCOMING_METHOD = "track:editor-content-changes"; + + private EditorWorkingCopyManager editorWorkingCopyManager; + + @Inject + public EditorChangesTracker(EditorWorkingCopyManager editorWorkingCopyManager) { + this.editorWorkingCopyManager = editorWorkingCopyManager; + } + + @Inject + public void configureHandler(RequestHandlerConfigurator configurator) { + configurator.newConfiguration() + .methodName(INCOMING_METHOD) + .paramsAsDto(EditorChangesDto.class) + .resultAsEmpty() + .withBiFunction((endpointId, changes) -> { + editorWorkingCopyManager.onEditorContentUpdated(endpointId, changes); + return null; + }); + } +} diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/EditorWorkingCopy.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/EditorWorkingCopy.java new file mode 100644 index 00000000000..7c43806ae32 --- /dev/null +++ b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/EditorWorkingCopy.java @@ -0,0 +1,170 @@ +/******************************************************************************* + * 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.api.project.server; + +import com.google.common.io.ByteStreams; + +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.project.shared.dto.EditorChangesDto; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +import static java.lang.String.format; +import static org.eclipse.che.api.project.shared.dto.EditorChangesDto.Type.INSERT; +import static org.eclipse.che.api.project.shared.dto.EditorChangesDto.Type.REMOVE; + +/** + * In-memory implementation of working copy for opened editor on client. + * + * @author Roman Nikitenko + */ +public class EditorWorkingCopy { + private String path; + private String projectPath; + private byte[] content; + + /** + * Creates a working copy for opened editor on client. + * + * @param path + * path to the persistent working copy + * @param projectPath + * path to the project which contains the opened editor on client + * @param content + * the content of the original file to creating working copy + */ + public EditorWorkingCopy(String path, String projectPath, byte[] content) { + this.path = path; + this.projectPath = projectPath; + this.content = Arrays.copyOf(content, content.length); + } + + /** + * Gets content of the working copy as bytes. + * + * @return content ot the working copy + */ + public byte[] getContentAsBytes() { + if (content == null) { + content = new byte[0]; + } + return Arrays.copyOf(content, content.length); + } + + /** + * Gets content of the working copy as String decoding bytes. + * + * @return content ot the working copy + */ + public String getContentAsString() { + return new String(getContentAsBytes()); + } + + /** + * Gets content of the working copy. + * + * @return content ot the working copy + */ + public InputStream getContent() { + return new ByteArrayInputStream(getContentAsBytes()); + } + + /** + * Updates content of the working copy. + * + * @param content + * content + * @return current working copy after updating content + */ + EditorWorkingCopy updateContent(byte[] content) { + this.content = content; + return this; + } + + /** + * Updates content of the working copy. + * + * @param content + * content + * @return current working copy after updating content + */ + EditorWorkingCopy updateContent(String content) { + updateContent(content.getBytes()); + return this; + } + + /** + * Updates content of the working copy. + * + * @param content + * content + * @return current working copy after updating content + */ + EditorWorkingCopy updateContent(InputStream content) throws ServerException { + byte[] bytes; + try { + bytes = ByteStreams.toByteArray(content); + } catch (IOException e) { + throw new ServerException(format("Can not update the content of '%s'. The reason is: %s", getPath(), e.getMessage())); + } + return updateContent(bytes); + } + + /** + * Updates content of the working copy by applying editor content changes. + * + * @param changes + * contains editor content changes + */ + void applyChanges(EditorChangesDto changes) { + synchronized (this) { + String text = changes.getText(); + int offset = changes.getOffset(); + int removedCharCount = changes.getRemovedCharCount(); + + String newContent = null; + String oldContent = getContentAsString(); + EditorChangesDto.Type type = changes.getType(); + if (type == INSERT) { + newContent = new StringBuilder(oldContent).insert(offset, text).toString(); + } + + if (type == REMOVE && removedCharCount > 0) { + newContent = new StringBuilder(oldContent).delete(offset, offset + removedCharCount).toString(); + } + + if (newContent != null) { + updateContent(newContent); + } + } + } + + /** Returns the path to the persistent working copy */ + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + /** Returns the path to the project which contains the opened editor on client */ + public String getProjectPath() { + return projectPath; + } + + public void setProjectPath(String projectPath) { + this.projectPath = projectPath; + } +} diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/EditorWorkingCopyManager.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/EditorWorkingCopyManager.java new file mode 100644 index 00000000000..bc889e0eea9 --- /dev/null +++ b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/EditorWorkingCopyManager.java @@ -0,0 +1,340 @@ +/******************************************************************************* + * 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.api.project.server; + +import com.google.common.hash.Hashing; + +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.ForbiddenException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.jsonrpc.commons.RequestTransmitter; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.core.notification.EventSubscriber; +import org.eclipse.che.api.project.shared.dto.EditorChangesDto; +import org.eclipse.che.api.project.shared.dto.ServerError; +import org.eclipse.che.api.project.shared.dto.event.FileTrackingOperationDto; +import org.eclipse.che.api.vfs.impl.file.event.detectors.FileTrackingOperationEvent; +import org.eclipse.che.commons.annotation.Nullable; +import org.eclipse.che.dto.server.DtoFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.PreDestroy; +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import static java.lang.String.format; +import static java.nio.charset.Charset.defaultCharset; +import static org.eclipse.che.api.project.shared.Constants.CHE_DIR; + +/** + * The class contains methods to simplify the work with editor working copies. + * + * @author Roman Nikitenko + */ +@Singleton +public class EditorWorkingCopyManager { + private static final Logger LOG = LoggerFactory.getLogger(EditorWorkingCopyManager.class); + private static final String WORKING_COPIES_DIR = "/" + CHE_DIR + "/workingCopies"; + private static final String WORKING_COPY_ERROR_METHOD = "track:editor-working-copy-error"; + + private Provider projectManagerProvider; + private EventService eventService; + private RequestTransmitter transmitter; + private EventSubscriber fileOperationEventSubscriber; + + private final Map workingCopiesStorage = new HashMap<>(); + + @Inject + public EditorWorkingCopyManager(Provider projectManagerProvider, + EventService eventService, + RequestTransmitter transmitter) { + this.projectManagerProvider = projectManagerProvider; + this.eventService = eventService; + this.transmitter = transmitter; + + fileOperationEventSubscriber = new EventSubscriber() { + @Override + public void onEvent(FileTrackingOperationEvent event) { + onFileOperation(event.getEndpointId(), event.getFileTrackingOperation()); + } + }; + eventService.subscribe(fileOperationEventSubscriber); + } + + /** + * Gets in-memory working copy by path to the original file. + * Note: returns {@code null} when working copy is not found + * + * @param filePath + * path to the original file + * @return in-memory working copy for the file which corresponds given {@code filePath} or {@code null} when working copy is not found + */ + @Nullable + public EditorWorkingCopy getWorkingCopy(String filePath) { + return workingCopiesStorage.get(filePath); + } + + void onEditorContentUpdated(String endpointId, EditorChangesDto changes) { + String filePath = changes.getFileLocation(); + String projectPath = changes.getProjectPath(); + + try { + if (filePath.isEmpty() || projectPath.isEmpty()) { + throw new NotFoundException("Paths for file and project should be defined"); + } + + EditorWorkingCopy workingCopy = workingCopiesStorage.get(filePath); + if (workingCopy == null) { + workingCopy = createWorkingCopy(filePath); + } + + workingCopy.applyChanges(changes); + eventService.publish(new EditorWorkingCopyUpdatedEvent(endpointId, changes)); + + } catch (IOException | ForbiddenException | ConflictException | ServerException e) { + String errorMessage = "Can not handle editor changes: " + e.getLocalizedMessage(); + + LOG.error(errorMessage); + + transmitError(500, errorMessage, endpointId); + } catch (NotFoundException e) { + String errorMessage = "Can not handle editor changes: " + e.getLocalizedMessage(); + + LOG.error(errorMessage); + + transmitError(400, errorMessage, endpointId); + } + } + + private void onFileOperation(String endpointId, FileTrackingOperationDto operation) { + try { + FileTrackingOperationDto.Type type = operation.getType(); + switch (type) { + case START: { + String path = operation.getPath(); + EditorWorkingCopy workingCopy = workingCopiesStorage.get(path); + if (workingCopy == null) { + createWorkingCopy(path); + } + //TODO At opening file we can have persistent working copy when user has unsaved data + // at this case we need provide ability to recover unsaved data + break; + } + case STOP: { + String path = operation.getPath(); + EditorWorkingCopy workingCopy = workingCopiesStorage.get(path); + if (workingCopy == null) { + return; + } + + if (isWorkingCopyHasUnsavedData(path)) { + createPersistentWorkingCopy(path);//to have ability to recover unsaved data when the file will be open later + } else { + VirtualFileEntry persistentWorkingCopy = getPersistentWorkingCopy(path, workingCopy.getProjectPath()); + if (persistentWorkingCopy != null) { + persistentWorkingCopy.remove(); + } + } + workingCopiesStorage.remove(path); + break; + } + + case MOVE: { + String oldPath = operation.getOldPath(); + String newPath = operation.getPath(); + + EditorWorkingCopy workingCopy = workingCopiesStorage.remove(oldPath); + if (workingCopy == null) { + return; + } + + String workingCopyNewPath = toWorkingCopyPath(newPath); + workingCopy.setPath(workingCopyNewPath); + workingCopiesStorage.put(newPath, workingCopy); + + String projectPath = workingCopy.getProjectPath(); + VirtualFileEntry persistentWorkingCopy = getPersistentWorkingCopy(oldPath, projectPath); + if (persistentWorkingCopy != null) { + persistentWorkingCopy.remove(); + } + break; + } + + default: { + break; + } + } + } catch (ServerException | IOException | ForbiddenException | ConflictException e) { + String errorMessage = "Can not handle file operation: " + e.getMessage(); + + LOG.error(errorMessage); + + transmitError(500, errorMessage, endpointId); + } catch (NotFoundException e) { + String errorMessage = "Can not handle file operation: " + e.getMessage(); + + LOG.error(errorMessage); + + transmitError(400, errorMessage, endpointId); + } + } + + private void transmitError(int code, String errorMessage, String endpointId) { + DtoFactory dtoFactory = DtoFactory.getInstance(); + ServerError error = dtoFactory.createDto(ServerError.class) + .withCode(code) + .withMessage(errorMessage); + transmitter.newRequest() + .endpointId(endpointId) + .methodName(WORKING_COPY_ERROR_METHOD) + .paramsAsDto(error) + .sendAndSkipResult(); + } + + private boolean isWorkingCopyHasUnsavedData(String originalFilePath) { + try { + EditorWorkingCopy workingCopy = workingCopiesStorage.get(originalFilePath); + if (workingCopy == null) { + return false; + } + + FileEntry originalFile = projectManagerProvider.get().asFile(originalFilePath); + if (originalFile == null) { + return false; + } + + String workingCopyContent = workingCopy.getContentAsString(); + String originalFileContent = originalFile.getVirtualFile().getContentAsString(); + if (workingCopyContent == null || originalFileContent == null) { + return false; + } + + String workingCopyHash = Hashing.md5().hashString(workingCopyContent, defaultCharset()).toString(); + String originalFileHash = Hashing.md5().hashString(originalFileContent, defaultCharset()).toString(); + + return !Objects.equals(workingCopyHash, originalFileHash); + } catch (NotFoundException | ServerException | ForbiddenException e) { + LOG.error(e.getLocalizedMessage()); + } + + return false; + } + + private EditorWorkingCopy createWorkingCopy(String filePath) + throws NotFoundException, ServerException, ConflictException, ForbiddenException, IOException { + + FileEntry file = projectManagerProvider.get().asFile(filePath); + if (file == null) { + throw new NotFoundException(format("Item '%s' isn't found. ", filePath)); + } + + String projectPath = file.getProject(); + String workingCopyPath = toWorkingCopyPath(filePath); + + EditorWorkingCopy workingCopy = new EditorWorkingCopy(workingCopyPath, projectPath, file.contentAsBytes()); + workingCopiesStorage.put(filePath, workingCopy); + + return workingCopy; + } + + private void createPersistentWorkingCopy(String originalFilePath) throws ServerException, ForbiddenException, ConflictException { + try { + EditorWorkingCopy workingCopy = workingCopiesStorage.get(originalFilePath); + if (workingCopy == null) { + throw new ServerException("Can not create recovery file for " + originalFilePath); + } + + byte[] content = workingCopy.getContentAsBytes(); + String projectPath = workingCopy.getProjectPath(); + + VirtualFileEntry persistentWorkingCopy = getPersistentWorkingCopy(originalFilePath, projectPath); + if (persistentWorkingCopy != null) { + persistentWorkingCopy.getVirtualFile().updateContent(content); + return; + } + + FolderEntry persistentWorkingCopiesStorage = getPersistentWorkingCopiesStorage(projectPath); + if (persistentWorkingCopiesStorage == null) { + persistentWorkingCopiesStorage = createPersistentWorkingCopiesStorage(projectPath); + } + + persistentWorkingCopiesStorage.createFile(workingCopy.getPath(), content); + } catch (ConflictException | ForbiddenException e) { + LOG.error(e.getLocalizedMessage()); + throw new ServerException("Can not create recovery file for " + originalFilePath); + } + } + + private VirtualFileEntry getPersistentWorkingCopy(String originalFilePath, String projectPath) { + try { + FolderEntry persistentWorkingCopiesStorage = getPersistentWorkingCopiesStorage(projectPath); + if (persistentWorkingCopiesStorage == null) { + return null; + } + + String workingCopyPath = toWorkingCopyPath(originalFilePath); + return persistentWorkingCopiesStorage.getChild(workingCopyPath); + } catch (ServerException e) { + LOG.error(e.getLocalizedMessage()); + return null; + } + } + + private FolderEntry getPersistentWorkingCopiesStorage(String projectPath) { + try { + RegisteredProject project = projectManagerProvider.get().getProject(projectPath); + FolderEntry baseFolder = project.getBaseFolder(); + if (baseFolder == null) { + return null; + } + + String tempDirectoryPath = baseFolder.getPath().toString() + WORKING_COPIES_DIR; + return projectManagerProvider.get().asFolder(tempDirectoryPath); + } catch (Exception e) { + LOG.error(e.getLocalizedMessage()); + return null; + } + } + + private FolderEntry createPersistentWorkingCopiesStorage(String projectPath) throws ServerException { + try { + RegisteredProject project = projectManagerProvider.get().getProject(projectPath); + FolderEntry baseFolder = project.getBaseFolder(); + if (baseFolder == null) { + throw new ServerException("Can not create storage for recovery data"); + } + + return baseFolder.createFolder(WORKING_COPIES_DIR); + } catch (NotFoundException | ConflictException | ForbiddenException e) { + LOG.error(e.getLocalizedMessage()); + throw new ServerException("Can not create storage for recovery data " + e.getMessage()); + } + } + + private String toWorkingCopyPath(String path) { + if (path.startsWith("/")) { + path = path.substring(1, path.length()); + } + return path.replace('/', '.'); + } + + @PreDestroy + private void unsubscribe() { + eventService.unsubscribe(fileOperationEventSubscriber); + } +} diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/EditorWorkingCopyUpdatedEvent.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/EditorWorkingCopyUpdatedEvent.java new file mode 100644 index 00000000000..a9707282f90 --- /dev/null +++ b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/EditorWorkingCopyUpdatedEvent.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * 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.api.project.server; + +import org.eclipse.che.api.project.shared.dto.EditorChangesDto; + +/** + * Notifies about changes of editor working copy. + * The event is used by {@link EditorWorkingCopyManager} when working copy is changed to notify interested consumers about it. + * + * @author Roman Nikitenko + */ +public class EditorWorkingCopyUpdatedEvent { + private final String endpointId; + private final EditorChangesDto textChange; + + /** Creates event which contains info about changes of editor working copy */ + EditorWorkingCopyUpdatedEvent(String endpointId, EditorChangesDto textChange) { + this.endpointId = endpointId; + this.textChange = textChange; + } + + public String getEndpointId() { + return endpointId; + } + + /** Returns changes of editor working copy. */ + public EditorChangesDto getChanges() { + return textChange; + } +} diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/FileEntry.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/FileEntry.java index 702c14724d7..abb63793e68 100644 --- a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/FileEntry.java +++ b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/FileEntry.java @@ -45,7 +45,7 @@ public InputStream getInputStream() throws IOException, ServerException { /** * Gets content of file as array of bytes. * - * @return content of file as stream + * @return content of file as array of bytes. * @throws ServerException * if other error occurs */ diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/ProjectApiModule.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/ProjectApiModule.java index ece894a01c9..192653cb551 100644 --- a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/ProjectApiModule.java +++ b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/project/server/ProjectApiModule.java @@ -93,6 +93,9 @@ protected void configure() { bind(FileWatcherNotificationHandler.class).to(DefaultFileWatcherNotificationHandler.class); + bind(EditorChangesTracker.class).asEagerSingleton(); + bind(EditorWorkingCopyManager.class).asEagerSingleton(); + configureVfsFilters(excludeMatcher); configureVfsFilters(fileWatcherExcludes); configureVfsEvent(); diff --git a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/detectors/EditorFileOperationHandler.java b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/detectors/EditorFileOperationHandler.java index 20ab4575594..946e291de02 100644 --- a/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/detectors/EditorFileOperationHandler.java +++ b/wsagent/che-core-api-project/src/main/java/org/eclipse/che/api/vfs/impl/file/event/detectors/EditorFileOperationHandler.java @@ -10,20 +10,27 @@ *******************************************************************************/ package org.eclipse.che.api.vfs.impl.file.event.detectors; +import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.jsonrpc.commons.JsonRpcException; import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerConfigurator; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.project.shared.dto.event.FileTrackingOperationDto; +import org.eclipse.che.api.project.shared.dto.event.FileTrackingOperationDto.Type; import javax.inject.Inject; import javax.inject.Singleton; +import static java.lang.String.format; +import static org.eclipse.che.api.project.shared.dto.event.FileTrackingOperationDto.Type.MOVE; +import static org.eclipse.che.api.project.shared.dto.event.FileTrackingOperationDto.Type.RESUME; +import static org.eclipse.che.api.project.shared.dto.event.FileTrackingOperationDto.Type.SUSPEND; + /** Receive a file tracking operation call from client and notify server side about it by {@link FileTrackingOperationEvent}. */ @Singleton public class EditorFileOperationHandler { private static final String INCOMING_METHOD = "track:editor-file"; - private final EventService eventService; + private final EventService eventService; @Inject public EditorFileOperationHandler(EventService eventService) { @@ -41,10 +48,25 @@ public void configureHandler(RequestHandlerConfigurator configurator) { private Void handleFileTrackingOperation(String endpointId, FileTrackingOperationDto operation) { try { + Type operationType = operation.getType(); + if (operationType == SUSPEND || operationType == RESUME) { + eventService.publish(new FileTrackingOperationEvent(endpointId, operation)); + return null; + } + + String filePath = operation.getPath(); + if (filePath.isEmpty()) { + throw new NotFoundException(format("Path for the file should be defined for %s operation", operationType)); + } + + if (operationType == MOVE && operation.getOldPath().isEmpty()) { + throw new NotFoundException("Old path should be defined for 'MOVE' operation"); + } + eventService.publish(new FileTrackingOperationEvent(endpointId, operation)); return null; } catch (Exception e) { - throw new JsonRpcException(500, e.getLocalizedMessage()); + throw new JsonRpcException(500, "Can not handle file operation: " + e.getLocalizedMessage()); } } }