diff --git a/ide/che-core-ide-api/src/main/resources/org/eclipse/che/ide/api/ui/style.css b/ide/che-core-ide-api/src/main/resources/org/eclipse/che/ide/api/ui/style.css index eacd84f4b86..a240ef23a58 100644 --- a/ide/che-core-ide-api/src/main/resources/org/eclipse/che/ide/api/ui/style.css +++ b/ide/che-core-ide-api/src/main/resources/org/eclipse/che/ide/api/ui/style.css @@ -167,7 +167,7 @@ @def dividerThickness 1px; @def popupPadding 24px; @def titlePaddingBottom 16px; -@def buttonSpacing 18px; +@def buttonSideSpacing 9px; @def labelPaddingBottom 6px; @def examplePaddingTop 4px; @def formButtonPaddingTop 18px; diff --git a/ide/che-core-ide-ui/src/main/java/org/eclipse/che/ide/ui/button/ButtonAlignment.java b/ide/che-core-ide-ui/src/main/java/org/eclipse/che/ide/ui/button/ButtonAlignment.java new file mode 100644 index 00000000000..381482b8640 --- /dev/null +++ b/ide/che-core-ide-ui/src/main/java/org/eclipse/che/ide/ui/button/ButtonAlignment.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2012-2017 Red Hat, Inc. + * 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: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.ide.ui.button; + +/** + * Describes button alignment in a horizontal container. + * + * @author Mykola Morhun + */ +public enum ButtonAlignment { + LEFT, + RIGHT +} diff --git a/ide/che-core-ide-ui/src/main/java/org/eclipse/che/ide/ui/window/Window.java b/ide/che-core-ide-ui/src/main/java/org/eclipse/che/ide/ui/window/Window.java index 0d39c08ad49..2f73be4e6f0 100644 --- a/ide/che-core-ide-ui/src/main/java/org/eclipse/che/ide/ui/window/Window.java +++ b/ide/che-core-ide-ui/src/main/java/org/eclipse/che/ide/ui/window/Window.java @@ -28,6 +28,7 @@ import com.google.gwt.user.client.ui.Widget; import elemental.js.dom.JsElement; import org.eclipse.che.commons.annotation.Nullable; +import org.eclipse.che.ide.ui.button.ButtonAlignment; import org.vectomatic.dom.svg.ui.SVGResource; /** @@ -154,11 +155,17 @@ public void setHideOnEscapeEnabled(boolean isEnabled) { } protected Button createButton(String title, String debugId, ClickHandler clickHandler) { + return createButton(title, debugId, clickHandler, ButtonAlignment.RIGHT); + } + + protected Button createButton( + String title, String debugId, ClickHandler clickHandler, ButtonAlignment alignment) { Button button = new Button(); button.setText(title); button.ensureDebugId(debugId); button.getElement().setId(debugId); button.addStyleName(resources.windowCss().button()); + addButtonAlignment(button, alignment); button.addClickHandler(clickHandler); //set default tab index button.setTabIndex(0); @@ -166,8 +173,14 @@ protected Button createButton(String title, String debugId, ClickHandler clickHa } protected Button createPrimaryButton(String title, String debugId, ClickHandler clickHandler) { + return createPrimaryButton(title, debugId, clickHandler, ButtonAlignment.RIGHT); + } + + protected Button createPrimaryButton( + String title, String debugId, ClickHandler clickHandler, ButtonAlignment alignment) { Button button = createButton(title, debugId, clickHandler); button.addStyleName(resources.windowCss().primaryButton()); + addButtonAlignment(button, alignment); //set default tab index button.setTabIndex(0); return button; @@ -243,6 +256,17 @@ public void execute() { } } + private void addButtonAlignment(Button button, ButtonAlignment alignment) { + switch (alignment) { + case LEFT: + button.addStyleName(resources.windowCss().buttonAlignLeft()); + break; + case RIGHT: + default: + button.addStyleName(resources.windowCss().buttonAlignRight()); + } + } + /** * Sets focus on the given element. If {@code elementToFocus} is {@code null}, no element will be * given focus @@ -343,6 +367,10 @@ public interface Css extends CssResource { String button(); + String buttonAlignLeft(); + + String buttonAlignRight(); + String image(); } diff --git a/ide/che-core-ide-ui/src/main/resources/org/eclipse/che/ide/ui/window/Window.css b/ide/che-core-ide-ui/src/main/resources/org/eclipse/che/ide/ui/window/Window.css index d2d4b75aeff..fcf8476a12e 100644 --- a/ide/che-core-ide-ui/src/main/resources/org/eclipse/che/ide/ui/window/Window.css +++ b/ide/che-core-ide-ui/src/main/resources/org/eclipse/che/ide/ui/window/Window.css @@ -113,8 +113,8 @@ } .alignBtn { - float: right; - margin-left: buttonSpacing; + margin-left: buttonSideSpacing; + margin-right: buttonSideSpacing; } .headerTitleWrapper { @@ -151,7 +151,6 @@ } .button, .primaryButton { - float: right; height: buttonHeight; line-height: buttonLineHeight; min-width: buttonMinWidth; @@ -194,6 +193,14 @@ opacity: 0.5; } +.buttonAlignLeft { + float: left; +} + +.buttonAlignRight { + float: right; +} + /* Blue button styles */ .primaryButton { background: primaryButtonBackground; diff --git a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/GitExtension.java b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/GitExtension.java index 2fdb7c92591..ad28491898e 100644 --- a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/GitExtension.java +++ b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/GitExtension.java @@ -33,6 +33,8 @@ import org.eclipse.che.ide.ext.git.client.action.FetchAction; import org.eclipse.che.ide.ext.git.client.action.HistoryAction; import org.eclipse.che.ide.ext.git.client.action.InitRepositoryAction; +import org.eclipse.che.ide.ext.git.client.action.NextDiffAction; +import org.eclipse.che.ide.ext.git.client.action.PreviousDiffAction; import org.eclipse.che.ide.ext.git.client.action.PullAction; import org.eclipse.che.ide.ext.git.client.action.PushAction; import org.eclipse.che.ide.ext.git.client.action.RemoveFromIndexAction; @@ -57,6 +59,9 @@ public class GitExtension { public static final String HISTORY_GROUP_MAIN_MENU = "GitHistoryGroup"; public static final String GIT_COMPARE_WITH_LATEST = "gitCompareWithLatest"; + public static final String NEXT_DIFF_ACTION_ID = "nextDiff"; + public static final String PREV_DIFF_ACTION_ID = "prevDiff"; + @Inject public GitExtension( GitResources resources, @@ -81,6 +86,8 @@ public GitExtension( CompareWithLatestAction compareWithLatestAction, CompareWithBranchAction compareWithBranchAction, CompareWithRevisionAction compareWithRevisionAction, + NextDiffAction nextDiffAction, + PreviousDiffAction previousDiffAction, KeyBindingAgent keyBinding, AppContext appContext) { @@ -180,8 +187,18 @@ public GitExtension( editorContextMenuGroup.addSeparator(); editorContextMenuGroup.add(gitContextMenuGroup); + actionManager.registerAction(NEXT_DIFF_ACTION_ID, nextDiffAction); + actionManager.registerAction(PREV_DIFF_ACTION_ID, previousDiffAction); + keyBinding .getGlobal() .addKey(new KeyBuilder().action().alt().charCode('d').build(), GIT_COMPARE_WITH_LATEST); + + keyBinding + .getGlobal() + .addKey(new KeyBuilder().alt().charCode('.').build(), NEXT_DIFF_ACTION_ID); + keyBinding + .getGlobal() + .addKey(new KeyBuilder().alt().charCode(',').build(), PREV_DIFF_ACTION_ID); } } diff --git a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/GitLocalizationConstant.java b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/GitLocalizationConstant.java index 9d3e38c35cd..3de6ed4188b 100644 --- a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/GitLocalizationConstant.java +++ b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/GitLocalizationConstant.java @@ -72,6 +72,15 @@ public interface GitLocalizationConstant extends Messages { @Key("button.compare") String buttonCompare(); + @Key("button.save_changes") + String buttonSaveChanges(); + + @Key("button.next_diff") + String buttonNextDiff(); + + @Key("button.previous_diff") + String buttonPreviousDiff(); + @Key("console.tooltip.clear") String buttonClear(); diff --git a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/action/CompareWithLatestAction.java b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/action/CompareWithLatestAction.java index 0012f3f692e..3691c5860b8 100644 --- a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/action/CompareWithLatestAction.java +++ b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/action/CompareWithLatestAction.java @@ -15,12 +15,9 @@ import static org.eclipse.che.api.git.shared.DiffType.NAME_STATUS; import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.NOT_EMERGE_MODE; import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL; -import static org.eclipse.che.ide.ext.git.client.compare.FileStatus.defineStatus; import com.google.inject.Inject; import com.google.inject.Singleton; -import java.util.HashMap; -import java.util.Map; import org.eclipse.che.ide.api.action.ActionEvent; import org.eclipse.che.ide.api.app.AppContext; import org.eclipse.che.ide.api.dialogs.DialogFactory; @@ -29,8 +26,8 @@ import org.eclipse.che.ide.api.resources.Project; import org.eclipse.che.ide.api.resources.Resource; import org.eclipse.che.ide.ext.git.client.GitLocalizationConstant; +import org.eclipse.che.ide.ext.git.client.compare.AlteredFiles; import org.eclipse.che.ide.ext.git.client.compare.ComparePresenter; -import org.eclipse.che.ide.ext.git.client.compare.FileStatus.Status; import org.eclipse.che.ide.ext.git.client.compare.changeslist.ChangesListPresenter; /** @@ -106,25 +103,13 @@ public void actionPerformed(ActionEvent e) { null) .show(); } else { - final String[] changedFiles = diff.split("\n"); - if (changedFiles.length == 1) { - project - .getFile(changedFiles[0].substring(2)) - .then( - file -> { - if (file.isPresent()) { - comparePresenter.showCompareWithLatest( - file.get(), - defineStatus(changedFiles[0].substring(0, 1)), - REVISION); - } - }); + AlteredFiles alteredFiles = new AlteredFiles(project, diff); + if (alteredFiles.getFilesQuantity() == 1) { + + comparePresenter.showCompareWithLatest(alteredFiles, null, REVISION); } else { - Map items = new HashMap<>(); - for (String item : changedFiles) { - items.put(item.substring(2, item.length()), defineStatus(item.substring(0, 1))); - } - changesListPresenter.show(items, REVISION, null, project); + + changesListPresenter.show(alteredFiles, REVISION, null); } } }) diff --git a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/action/NextDiffAction.java b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/action/NextDiffAction.java new file mode 100644 index 00000000000..a5e088f813d --- /dev/null +++ b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/action/NextDiffAction.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2012-2017 Red Hat, Inc. + * 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: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.ide.ext.git.client.action; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import org.eclipse.che.ide.api.action.Action; +import org.eclipse.che.ide.api.action.ActionEvent; +import org.eclipse.che.ide.ext.git.client.compare.ComparePresenter; + +/** @author Mykola Morhun */ +@Singleton +public class NextDiffAction extends Action { + + private final ComparePresenter comparePresenter; + + @Inject + public NextDiffAction(ComparePresenter comparePresenter) { + this.comparePresenter = comparePresenter; + } + + @Override + public void actionPerformed(ActionEvent e) { + if (comparePresenter.isShown()) { + comparePresenter.onNextDiffClicked(); + } + } +} diff --git a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/action/PreviousDiffAction.java b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/action/PreviousDiffAction.java new file mode 100644 index 00000000000..4994373cec3 --- /dev/null +++ b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/action/PreviousDiffAction.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2012-2017 Red Hat, Inc. + * 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: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.ide.ext.git.client.action; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import org.eclipse.che.ide.api.action.Action; +import org.eclipse.che.ide.api.action.ActionEvent; +import org.eclipse.che.ide.ext.git.client.compare.ComparePresenter; + +/** @author Mykola Morhun */ +@Singleton +public class PreviousDiffAction extends Action { + + private final ComparePresenter comparePresenter; + + @Inject + public PreviousDiffAction(ComparePresenter comparePresenter) { + super(null, null); + this.comparePresenter = comparePresenter; + } + + @Override + public void actionPerformed(ActionEvent e) { + if (comparePresenter.isShown()) { + comparePresenter.onPreviousDiffClicked(); + } + } +} diff --git a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/commit/CommitPresenter.java b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/commit/CommitPresenter.java index d7fd66aa0ba..5e979765825 100644 --- a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/commit/CommitPresenter.java +++ b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/commit/CommitPresenter.java @@ -10,7 +10,6 @@ */ package org.eclipse.che.ide.ext.git.client.commit; -import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.collect.Iterables.getFirst; import static java.util.Arrays.stream; import static java.util.Collections.singletonList; @@ -21,16 +20,12 @@ import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.NOT_EMERGE_MODE; import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL; import static org.eclipse.che.ide.api.notification.StatusNotification.Status.SUCCESS; -import static org.eclipse.che.ide.ext.git.client.compare.FileStatus.defineStatus; import static org.eclipse.che.ide.util.ExceptionUtils.getErrorCode; import com.google.inject.Inject; import com.google.inject.Singleton; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; import javax.validation.constraints.NotNull; import org.eclipse.che.api.core.ErrorCodes; @@ -44,7 +39,7 @@ import org.eclipse.che.ide.commons.exception.ServerException; import org.eclipse.che.ide.ext.git.client.DateTimeFormatter; import org.eclipse.che.ide.ext.git.client.GitLocalizationConstant; -import org.eclipse.che.ide.ext.git.client.compare.FileStatus.Status; +import org.eclipse.che.ide.ext.git.client.compare.AlteredFiles; import org.eclipse.che.ide.ext.git.client.compare.changespanel.ChangesPanelPresenter; import org.eclipse.che.ide.ext.git.client.outputconsole.GitOutputConsole; import org.eclipse.che.ide.ext.git.client.outputconsole.GitOutputConsoleFactory; @@ -74,7 +69,7 @@ public class CommitPresenter implements CommitView.ActionDelegate { private final ProcessesPanelPresenter consolesPanelPresenter; private Project project; - private Set allFiles; + private List allFiles; private List filesToCommit; @Inject @@ -140,7 +135,7 @@ public void showDialog(Project project) { List newFiles = new ArrayList<>(); newFiles.addAll(status.getAdded()); newFiles.addAll(status.getUntracked()); - show(newFiles.stream().collect(joining("\nA ", "A ", ""))); + show(newFiles.stream().collect(joining("\nA\t", "A\t", ""))); }); } }); @@ -177,31 +172,20 @@ private void showAskForAmendDialog() { } private void show(@Nullable String diff) { - Map files = toFileStatusMap(diff); + AlteredFiles alteredFiles = new AlteredFiles(project, diff); filesToCommit.clear(); - allFiles = files.keySet(); + allFiles = alteredFiles.getAlteredFilesList(); view.setEnableCommitButton(!view.getMessage().isEmpty()); view.focusInMessageField(); view.showDialog(); - changesPanelPresenter.show(files); + changesPanelPresenter.show(alteredFiles); view.setMarkedCheckBoxes( stream(appContext.getResources()) .map(resource -> resource.getLocation().removeFirstSegments(1)) .collect(Collectors.toSet())); } - private Map toFileStatusMap(@Nullable String diff) { - Map items = new HashMap<>(); - if (!isNullOrEmpty(diff)) { - stream(diff.split("\n")) - .forEach( - item -> - items.put(item.substring(2, item.length()), defineStatus(item.substring(0, 1)))); - } - return items; - } - @Override public void onCommitClicked() { Path location = project.getLocation(); @@ -305,7 +289,7 @@ public void onFileNodeCheckBoxValueChanged(Path path, boolean newCheckBoxValue) } @Override - public Set getChangedFiles() { + public List getChangedFiles() { return allFiles; } diff --git a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/commit/CommitView.java b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/commit/CommitView.java index cecf285b13f..6c141175e26 100644 --- a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/commit/CommitView.java +++ b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/commit/CommitView.java @@ -49,7 +49,7 @@ interface ActionDelegate { void setAmendCommitMessage(); /** Get list of changed files paths. */ - Set getChangedFiles(); + List getChangedFiles(); } /** @return entered message */ diff --git a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/AlteredFiles.java b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/AlteredFiles.java new file mode 100644 index 00000000000..8c0176ec97a --- /dev/null +++ b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/AlteredFiles.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2012-2017 Red Hat, Inc. + * 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: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.ide.ext.git.client.compare; + +import static com.google.common.base.Strings.isNullOrEmpty; +import static org.eclipse.che.ide.ext.git.client.compare.FileStatus.defineStatus; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import org.eclipse.che.ide.api.resources.Project; +import org.eclipse.che.ide.ext.git.client.compare.FileStatus.Status; + +/** + * Describes changed in any way files in git comparison process. + * + * @author Mykola Morhun + */ +public class AlteredFiles { + + private final Project project; + private final LinkedHashMap alteredFilesStatuses; + private final List alteredFilesList; + + /** + * Parses raw git diff string and creates advanced representation. + * + * @param project the project under diff operation + * @param diff plain result of git diff operation + */ + public AlteredFiles(Project project, String diff) { + this.project = project; + + alteredFilesStatuses = new LinkedHashMap<>(); + if (!isNullOrEmpty(diff)) { + for (String item : diff.split("\n")) { + if (item.length() < 3 || item.charAt(1) != '\t') { + throw new IllegalArgumentException("Invalid git diff format. Invalid record: " + item); + } + alteredFilesStatuses.put( + item.substring(2, item.length()), defineStatus(item.substring(0, 1))); + } + } + + alteredFilesList = new ArrayList<>(alteredFilesStatuses.keySet()); + } + + /** Returns project in which git repository is located. */ + public Project getProject() { + return project; + } + + /** Returns number of files in the diff. */ + public int getFilesQuantity() { + return alteredFilesList.size(); + } + + public boolean isEmpty() { + return 0 == alteredFilesList.size(); + } + + /** Returns this diff in map representation: altered file to its git status. */ + public Map getChangedFilesMap() { + return alteredFilesStatuses; + } + + /** Returns list of altered files in this git diff. */ + public List getAlteredFilesList() { + return alteredFilesList; + } + + public Status getStatusByFilePath(String relativePathToChangedFile) { + return alteredFilesStatuses.get(relativePathToChangedFile); + } + + public Status getStatusByIndex(int index) { + return alteredFilesStatuses.get(alteredFilesList.get(index)); + } + + public String getFileByIndex(int index) { + return alteredFilesList.get(index); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AlteredFiles that = (AlteredFiles) o; + return Objects.equals(project, that.project) + && Objects.equals(alteredFilesStatuses, that.alteredFilesStatuses) + && Objects.equals(alteredFilesList, that.alteredFilesList); + } + + @Override + public int hashCode() { + return Objects.hash(project, alteredFilesStatuses, alteredFilesList); + } +} diff --git a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/ComparePresenter.java b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/ComparePresenter.java index 336da6874f6..445097afaaf 100644 --- a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/ComparePresenter.java +++ b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/ComparePresenter.java @@ -19,7 +19,6 @@ import com.google.inject.Singleton; import com.google.web.bindery.event.shared.EventBus; import org.eclipse.che.commons.annotation.Nullable; -import org.eclipse.che.ide.api.app.AppContext; import org.eclipse.che.ide.api.dialogs.CancelCallback; import org.eclipse.che.ide.api.dialogs.ConfirmCallback; import org.eclipse.che.ide.api.dialogs.DialogFactory; @@ -29,7 +28,6 @@ import org.eclipse.che.ide.api.resources.Container; import org.eclipse.che.ide.api.resources.File; import org.eclipse.che.ide.ext.git.client.GitLocalizationConstant; -import org.eclipse.che.ide.ext.git.client.GitUtil; import org.eclipse.che.ide.ext.git.client.compare.FileStatus.Status; import org.eclipse.che.ide.resource.Path; @@ -38,11 +36,11 @@ * * @author Igor Vinokur * @author Vlad Zhukovskyi + * @author Mykola Morhun */ @Singleton public class ComparePresenter implements CompareView.ActionDelegate { - private final AppContext appContext; private final EventBus eventBus; private final DialogFactory dialogFactory; private final CompareView view; @@ -50,21 +48,24 @@ public class ComparePresenter implements CompareView.ActionDelegate { private final GitLocalizationConstant locale; private final NotificationManager notificationManager; + private boolean compareWithLatest; + private AlteredFiles alteredFiles; + private int currentFileIndex; private File comparedFile; private String revision; private String localContent; - private boolean compareWithLatest; + private String revisionA; + private String revisionB; @Inject public ComparePresenter( - AppContext appContext, EventBus eventBus, DialogFactory dialogFactory, CompareView view, GitServiceClient service, GitLocalizationConstant locale, NotificationManager notificationManager) { - this.appContext = appContext; + this.eventBus = eventBus; this.dialogFactory = dialogFactory; this.view = view; @@ -74,41 +75,110 @@ public ComparePresenter( this.view.setDelegate(this); } + public boolean isShown() { + return view.isVisible(); + } + /** - * Show compare window. + * Show compare window for given set of files between given revision and HEAD. * - * @param file file name with its full path - * @param status status of the file + * @param alteredFiles ordered changed files + * @param currentFile file which will be shown first, if null then the first from the list will be + * shown * @param revision hash of revision or branch */ - public void showCompareWithLatest(final File file, final Status status, final String revision) { - this.comparedFile = file; + public void showCompareWithLatest( + final AlteredFiles alteredFiles, @Nullable final String currentFile, final String revision) { + this.alteredFiles = alteredFiles; this.revision = revision; + this.compareWithLatest = true; - if (status.equals(ADDED)) { - showCompare(""); - return; - } + currentFileIndex = findFileIndexOrFirst(currentFile); + showCompareForCurrentFile(); + } - final Container rootProject = GitUtil.getRootProject(file); + /** + * Shows compare window for given set of files between specified revisions. + * + * @param alteredFiles ordered changed files + * @param currentFile file which will be shown first, if null then the first from the list will be + * shown + * @param revisionA hash of the first revision or branch. If it is set to {@code null}, compare + * with empty repository state will be performed + * @param revisionB hash of the second revision or branch. If it is set to {@code null}, compare + * with latest repository state will be performed + */ + public void showCompareBetweenRevisions( + final AlteredFiles alteredFiles, + @Nullable final String currentFile, + @Nullable final String revisionA, + @Nullable final String revisionB) { + this.alteredFiles = alteredFiles; + this.revisionA = revisionA; + this.revisionB = revisionB; - if (rootProject == null) { + this.compareWithLatest = false; + + currentFileIndex = findFileIndexOrFirst(currentFile); + showCompareForCurrentFile(); + } + + /** + * Shows comparison for selected file. Type of comparison to show depends on {@code + * compareWithLatest} field. + */ + private void showCompareForCurrentFile() { + view.setEnableNextDiffButton(currentFileIndex != (alteredFiles.getFilesQuantity() - 1)); + view.setEnablePreviousDiffButton(currentFileIndex != 0); + + alteredFiles + .getProject() + .getFile(alteredFiles.getFileByIndex(currentFileIndex)) + .then( + file -> { + if (file.isPresent()) { + this.comparedFile = file.get(); + view.setEnableSaveChangesButton(true); + } else { // file is deleted + this.comparedFile = null; + view.setEnableSaveChangesButton(false); + } + + // For now git repository supported only in project root folder + final Path gitDirLocation = alteredFiles.getProject().getLocation(); + final Path relPath = Path.valueOf(alteredFiles.getFileByIndex(currentFileIndex)); + + if (compareWithLatest) { + showCompareWithLatestForFile( + gitDirLocation, relPath, alteredFiles.getStatusByIndex(currentFileIndex)); + } else { + showCompareBetweenRevisionsForFile( + gitDirLocation, relPath, alteredFiles.getStatusByIndex(currentFileIndex)); + } + }) + .catchError( + error -> { + notificationManager.notify(error.getMessage(), FAIL, NOT_EMERGE_MODE); + }); + } + + private void showCompareWithLatestForFile( + final Path gitDirLocation, final Path relPath, final Status status) { + if (status.equals(ADDED)) { + showCompare(""); return; } - final Path relPath = - file.getLocation().removeFirstSegments(rootProject.getLocation().segmentCount()); - if (status.equals(DELETED)) { service - .showFileContent(rootProject.getLocation(), relPath, revision) + .showFileContent(gitDirLocation, relPath, revision) .then( content -> { - view.setTitle(file.getLocation().toString()); + view.setTitle(getTitleForFile(relPath.toString())); view.setColumnTitles( locale.compareYourVersionTitle(), revision + locale.compareReadOnlyTitle()); - view.show(content.getContent(), "", file.getLocation().toString(), false); + view.show(content.getContent(), "", relPath.toString(), true); }) .catchError( error -> { @@ -116,7 +186,7 @@ public void showCompareWithLatest(final File file, final Status status, final St }); } else { service - .showFileContent(rootProject.getLocation(), relPath, revision) + .showFileContent(gitDirLocation, relPath, revision) .then( content -> { showCompare(content.getContent()); @@ -128,33 +198,19 @@ public void showCompareWithLatest(final File file, final Status status, final St } } - /** - * @param file path of the file - * @param status status of the file - * @param revisionA hash of the first revision or branch. If it is set to {@code null}, compare - * with empty repository state will be performed - * @param revisionB hash of the second revision or branch. If it is set to {@code null}, compare - * with latest repository state will be performed - */ - public void showCompareBetweenRevisions( - final Path file, - final Status status, - @Nullable final String revisionA, - @Nullable final String revisionB) { - this.compareWithLatest = false; - - final Path projectLocation = appContext.getRootProject().getLocation(); + private void showCompareBetweenRevisionsForFile( + final Path gitDir, final Path relPath, final Status status) { + view.setTitle(getTitleForFile(relPath.toString())); - view.setTitle(file.toString()); if (status == Status.ADDED) { service - .showFileContent(projectLocation, file, revisionB) + .showFileContent(gitDir, relPath, revisionB) .then( response -> { view.setColumnTitles( revisionB + locale.compareReadOnlyTitle(), revisionA == null ? "" : revisionA + locale.compareReadOnlyTitle()); - view.show("", response.getContent(), file.toString(), true); + view.show("", response.getContent(), relPath.toString(), true); }) .catchError( error -> { @@ -162,13 +218,13 @@ public void showCompareBetweenRevisions( }); } else if (status == Status.DELETED) { service - .showFileContent(projectLocation, file, revisionA) + .showFileContent(gitDir, relPath, revisionA) .then( response -> { view.setColumnTitles( revisionB + locale.compareReadOnlyTitle(), revisionA + locale.compareReadOnlyTitle()); - view.show(response.getContent(), "", file.toString(), true); + view.show(response.getContent(), "", relPath.toString(), true); }) .catchError( error -> { @@ -176,11 +232,11 @@ public void showCompareBetweenRevisions( }); } else { service - .showFileContent(projectLocation, file, revisionA) + .showFileContent(gitDir, relPath, revisionA) .then( contentAResponse -> { service - .showFileContent(projectLocation, file, revisionB) + .showFileContent(gitDir, relPath, revisionB) .then( contentBResponse -> { view.setColumnTitles( @@ -189,7 +245,7 @@ public void showCompareBetweenRevisions( view.show( contentAResponse.getContent(), contentBResponse.getContent(), - file.toString(), + relPath.toString(), true); }) .catchError( @@ -201,28 +257,19 @@ public void showCompareBetweenRevisions( } @Override - public void onClose(final String newContent) { - if (!compareWithLatest || this.localContent == null || newContent.equals(localContent)) { + public void onClose() { + final String newContent = view.getEditableContent(); + + if (!isSaveNeeded(newContent)) { view.hide(); return; } ConfirmCallback confirmCallback = - () -> - comparedFile - .updateContent(newContent) - .then( - ignored -> { - final Container parent = comparedFile.getParent(); - - if (parent != null) { - parent.synchronize(); - } - - eventBus.fireEvent( - new FileContentUpdateEvent(comparedFile.getLocation().toString())); - view.hide(); - }); + () -> { + saveContent(newContent); + view.hide(); + }; CancelCallback cancelCallback = view::hide; @@ -237,6 +284,36 @@ public void onClose(final String newContent) { .show(); } + @Override + public void onSaveChangesClicked() { + if (compareWithLatest) { + final String content = view.getEditableContent(); + if (isSaveNeeded(content)) { + saveContent(content); + } + } + } + + @Override + public void onNextDiffClicked() { + if (currentFileIndex < alteredFiles.getFilesQuantity() - 1) { + onSaveChangesClicked(); + + currentFileIndex++; + showCompareForCurrentFile(); + } + } + + @Override + public void onPreviousDiffClicked() { + if (currentFileIndex > 0) { + onSaveChangesClicked(); + + currentFileIndex--; + showCompareForCurrentFile(); + } + } + private void showCompare(final String remoteContent) { comparedFile .getContent() @@ -244,10 +321,68 @@ private void showCompare(final String remoteContent) { local -> { localContent = local; final String path = comparedFile.getLocation().removeFirstSegments(1).toString(); - view.setTitle(path); + view.setTitle(getTitleForFile(path)); view.setColumnTitles( locale.compareYourVersionTitle(), revision + locale.compareReadOnlyTitle()); view.show(remoteContent, localContent, path, false); }); } + + /** + * Searches for given file in the changes files list and save it sequential number to class field. + * + * @param currentFile name of file to set up as current; if null or invalid, the first one will be + * chosen. + * @return given file index or first index if specified file isn't found. + */ + private int findFileIndexOrFirst(@Nullable String currentFile) { + if (currentFile == null) { + return 0; + } + + int fileIndex = alteredFiles.getAlteredFilesList().indexOf(currentFile); + if (fileIndex == -1) { + return 0; + } + return fileIndex; + } + + /** Returns true if is required to save new content. */ + private boolean isSaveNeeded(final String newContent) { + return compareWithLatest + && comparedFile != null + && this.localContent != null + && !newContent.equals(localContent); + } + + /** Saves given contents into file under edit. */ + private void saveContent(final String content) { + localContent = content; + comparedFile + .updateContent(content) + .then( + ignored -> { + final Container parent = comparedFile.getParent(); + + if (parent != null) { + parent.synchronize(); + } + + eventBus.fireEvent(new FileContentUpdateEvent(comparedFile.getLocation().toString())); + }) + .catchError( + error -> { + notificationManager.notify(error.getMessage(), FAIL, NOT_EMERGE_MODE); + }); + } + + private String getTitleForFile(String file) { + return "Review diff in: " + + file + + " (" + + (currentFileIndex + 1) + + '/' + + alteredFiles.getFilesQuantity() + + ')'; + } } diff --git a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/CompareView.java b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/CompareView.java index a86e2012278..2bff5ac6dbf 100644 --- a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/CompareView.java +++ b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/CompareView.java @@ -21,6 +21,23 @@ @ImplementedBy(CompareViewImpl.class) interface CompareView extends View { + interface ActionDelegate { + /** Performs some actions in response to user's closing the window. */ + void onClose(); + + /** Performs save of editable panel in diff dialog. Does nothing if content isn't editable. */ + void onSaveChangesClicked(); + + /** Shows next diff. */ + void onNextDiffClicked(); + + /** Shows previous diff. */ + void onPreviousDiffClicked(); + } + + /** Returns content of editable part of the widget */ + String getEditableContent(); + /** * Set a title for the window. * @@ -39,22 +56,25 @@ interface CompareView extends View { /** Hide compare window. */ void hide(); + /** Shows whether widget is opened */ + boolean isVisible(); + /** * Show compare window with specified contents. * * @param oldContent content from specified revision or branch * @param newContent content of current file * @param file changed file name with its full path - * @param readOnly read only state of the right column + * @param readOnly read only state of the left column */ void show(String oldContent, String newContent, String file, boolean readOnly); - interface ActionDelegate { - /** - * Performs some actions in response to user's closing the window. - * - * @param newContent new content of compare widget - */ - void onClose(String newContent); - } + /** Change the enable state of the Save Changes button */ + void setEnableSaveChangesButton(boolean enabled); + + /** Change the enable state of the Next Diff button */ + void setEnableNextDiffButton(boolean enabled); + + /** Change the enable state of the Previous Diff button */ + void setEnablePreviousDiffButton(boolean enabled); } diff --git a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/CompareViewImpl.java b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/CompareViewImpl.java index 55d5149a65a..2a3c3aada32 100644 --- a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/CompareViewImpl.java +++ b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/CompareViewImpl.java @@ -10,12 +10,14 @@ */ package org.eclipse.che.ide.ext.git.client.compare; +import static org.eclipse.che.ide.orion.compare.CompareInitializer.GIT_COMPARE_MODULE; + import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.dom.client.Document; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.DockLayoutPanel; import com.google.gwt.user.client.ui.Label; @@ -23,15 +25,15 @@ import com.google.gwt.user.client.ui.Widget; import com.google.inject.Inject; import com.google.inject.Singleton; -import org.eclipse.che.ide.api.theme.ThemeAgent; import org.eclipse.che.ide.ext.git.client.GitLocalizationConstant; import org.eclipse.che.ide.orion.compare.CompareConfig; import org.eclipse.che.ide.orion.compare.CompareFactory; -import org.eclipse.che.ide.orion.compare.CompareWidget; -import org.eclipse.che.ide.orion.compare.CompareWidget.ContentCallBack; +import org.eclipse.che.ide.orion.compare.CompareInitializer; import org.eclipse.che.ide.orion.compare.FileOptions; -import org.eclipse.che.ide.ui.loaders.request.LoaderFactory; +import org.eclipse.che.ide.orion.compare.jso.GitCompareOverlay; +import org.eclipse.che.ide.ui.button.ButtonAlignment; import org.eclipse.che.ide.ui.window.Window; +import org.eclipse.che.requirejs.ModuleHolder; /** * Implementation of {@link CompareView}. @@ -54,50 +56,61 @@ interface PreviewViewImplUiBinder extends UiBinder {} @UiField(provided = true) final GitLocalizationConstant locale; - private ActionDelegate delegate; - private ThemeAgent themeAgent; - private CompareWidget compare; + private final Button btnSaveChanges; + private final Button btnNextDiff; + private final Button btnPrevDiff; + private final ModuleHolder moduleHolder; + private final CompareInitializer compareInitializer; private final CompareFactory compareFactory; - private final LoaderFactory loaderFactory; + + private ActionDelegate delegate; + private GitCompareOverlay compareWidget; + private boolean visible; @Inject public CompareViewImpl( CompareFactory compareFactory, GitLocalizationConstant locale, - LoaderFactory loaderFactory, - ThemeAgent themeAgent) { + CompareInitializer compareInitializer, + ModuleHolder moduleHolder) { this.compareFactory = compareFactory; this.locale = locale; - this.loaderFactory = loaderFactory; - this.themeAgent = themeAgent; + this.compareInitializer = compareInitializer; + this.moduleHolder = moduleHolder; setWidget(UI_BINDER.createAndBindUi(this)); Button closeButton = + createButton(locale.buttonClose(), "git-compare-close-btn", event -> onClose()); + Button refreshButton = createButton( - locale.buttonClose(), - "git-compare-close-btn", - new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - onClose(); - } - }); + locale.buttonRefresh(), "git-compare-refresh-btn", event -> compareWidget.refresh()); - Button refreshButton = + btnSaveChanges = + createButton( + locale.buttonSaveChanges(), + "git-compare-save-changes-btn", + event -> delegate.onSaveChangesClicked()); + btnNextDiff = + createButton( + locale.buttonNextDiff(), + "git-compare-next-diff-btn", + event -> delegate.onNextDiffClicked(), + ButtonAlignment.LEFT); + btnPrevDiff = createButton( - locale.buttonRefresh(), - "git-compare-refresh-btn", - new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - compare.refresh(); - } - }); + locale.buttonPreviousDiff(), + "git-compare-prev-diff-btn", + event -> delegate.onPreviousDiffClicked(), + ButtonAlignment.LEFT); addButtonToFooter(closeButton); addButtonToFooter(refreshButton); + addButtonToFooter(btnSaveChanges); + + addButtonToFooter(btnPrevDiff); + addButtonToFooter(btnNextDiff); comparePanel.getElement().setId(Document.get().createUniqueId()); } @@ -109,13 +122,13 @@ public void setDelegate(ActionDelegate delegate) { @Override protected void onClose() { - compare.getContent( - new ContentCallBack() { - @Override - public void onContentReceived(String content) { - delegate.onClose(content); - } - }); + visible = false; + delegate.onClose(); + } + + @Override + public String getEditableContent() { + return compareWidget.getContent(); } @Override @@ -124,6 +137,11 @@ public void setColumnTitles(String leftTitle, String rightTitle) { this.rightTitle.setText(rightTitle); } + @Override + public boolean isVisible() { + return visible; + } + @Override public void show(String oldContent, String newContent, String fileName, boolean readOnly) { dockPanel.setSize( @@ -131,6 +149,7 @@ public void show(String oldContent, String newContent, String fileName, boolean String.valueOf((com.google.gwt.user.client.Window.getClientHeight() / 100) * 90) + "px"); super.show(); + visible = true; FileOptions newFile = compareFactory.createFieOptions(); newFile.setReadOnly(readOnly); @@ -149,8 +168,32 @@ public void show(String oldContent, String newContent, String fileName, boolean compareConfig.setShowTitle(false); compareConfig.setShowLineStatus(false); - compare = new CompareWidget(compareConfig, themeAgent.getCurrentThemeId(), loaderFactory); - comparePanel.clear(); - comparePanel.add(compare); + compareInitializer.injectCompareWidget( + new AsyncCallback() { + @Override + public void onSuccess(Void result) { + JavaScriptObject gitCompare = moduleHolder.getModule(GIT_COMPARE_MODULE); + compareWidget = + GitCompareOverlay.create(gitCompare, compareConfig, "gwt-debug-compareParentDiv"); + } + + @Override + public void onFailure(Throwable caught) {} + }); + } + + @Override + public void setEnableSaveChangesButton(boolean enabled) { + btnSaveChanges.setEnabled(enabled); + } + + @Override + public void setEnableNextDiffButton(boolean enabled) { + btnNextDiff.setEnabled(enabled); + } + + @Override + public void setEnablePreviousDiffButton(boolean enabled) { + btnPrevDiff.setEnabled(enabled); } } diff --git a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/CompareViewImpl.ui.xml b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/CompareViewImpl.ui.xml index 51a6d16979d..4bc2d3d1297 100644 --- a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/CompareViewImpl.ui.xml +++ b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/CompareViewImpl.ui.xml @@ -13,6 +13,15 @@ + + .compareContainer { + position: absolute; + bottom: 0; + width: 100%; + height: 100%; + overflow-y: auto; + } + @@ -26,7 +35,9 @@ - + + + diff --git a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/branchlist/BranchListPresenter.java b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/branchlist/BranchListPresenter.java index aab512bc984..190b5a1b679 100644 --- a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/branchlist/BranchListPresenter.java +++ b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/branchlist/BranchListPresenter.java @@ -16,12 +16,9 @@ import static org.eclipse.che.api.git.shared.DiffType.NAME_STATUS; import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.NOT_EMERGE_MODE; import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL; -import static org.eclipse.che.ide.ext.git.client.compare.FileStatus.defineStatus; import com.google.inject.Inject; import com.google.inject.Singleton; -import java.util.HashMap; -import java.util.Map; import javax.validation.constraints.NotNull; import org.eclipse.che.api.git.shared.Branch; import org.eclipse.che.ide.api.app.AppContext; @@ -31,8 +28,8 @@ import org.eclipse.che.ide.api.resources.Project; import org.eclipse.che.ide.api.resources.Resource; import org.eclipse.che.ide.ext.git.client.GitLocalizationConstant; +import org.eclipse.che.ide.ext.git.client.compare.AlteredFiles; import org.eclipse.che.ide.ext.git.client.compare.ComparePresenter; -import org.eclipse.che.ide.ext.git.client.compare.FileStatus.Status; import org.eclipse.che.ide.ext.git.client.compare.changeslist.ChangesListPresenter; import org.eclipse.che.ide.ext.git.client.outputconsole.GitOutputConsole; import org.eclipse.che.ide.ext.git.client.outputconsole.GitOutputConsoleFactory; @@ -134,25 +131,12 @@ public void onCompareClicked() { null) .show(); } else { - final String[] changedFiles = diff.split("\n"); - if (changedFiles.length == 1) { - project - .getFile(changedFiles[0].substring(2)) - .then( - file -> { - if (file.isPresent()) { - comparePresenter.showCompareWithLatest( - file.get(), - defineStatus(changedFiles[0].substring(0, 1)), - selectedBranch.getName()); - } - }); + AlteredFiles alteredFiles = new AlteredFiles(project, diff); + if (alteredFiles.getFilesQuantity() == 1) { + comparePresenter.showCompareWithLatest( + alteredFiles, null, selectedBranch.getName()); } else { - Map items = new HashMap<>(); - for (String item : changedFiles) { - items.put(item.substring(2, item.length()), defineStatus(item.substring(0, 1))); - } - changesListPresenter.show(items, selectedBranch.getName(), null, project); + changesListPresenter.show(alteredFiles, selectedBranch.getName(), null); } } }) diff --git a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/changeslist/ChangesListPresenter.java b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/changeslist/ChangesListPresenter.java index ccf39889981..3f43bdd3283 100644 --- a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/changeslist/ChangesListPresenter.java +++ b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/changeslist/ChangesListPresenter.java @@ -11,53 +11,44 @@ package org.eclipse.che.ide.ext.git.client.compare.changeslist; import static com.google.common.collect.Iterables.getFirst; -import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.NOT_EMERGE_MODE; -import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL; import com.google.inject.Inject; import com.google.inject.Singleton; -import java.util.Map; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.ide.api.data.tree.Node; -import org.eclipse.che.ide.api.notification.NotificationManager; -import org.eclipse.che.ide.api.resources.Project; +import org.eclipse.che.ide.ext.git.client.compare.AlteredFiles; import org.eclipse.che.ide.ext.git.client.compare.ComparePresenter; -import org.eclipse.che.ide.ext.git.client.compare.FileStatus.Status; -import org.eclipse.che.ide.ext.git.client.compare.changespanel.ChangedFileNode; import org.eclipse.che.ide.ext.git.client.compare.changespanel.ChangedFolderNode; import org.eclipse.che.ide.ext.git.client.compare.changespanel.ChangesPanelPresenter; import org.eclipse.che.ide.ext.git.client.compare.changespanel.ChangesPanelView; -import org.eclipse.che.ide.resource.Path; import org.eclipse.che.ide.ui.smartTree.event.SelectionChangedEvent.SelectionChangedHandler; /** - * Presenter for displaying list of changed files. + * Presenter for displaying window with list of changed files. * * @author Igor Vinokur * @author Vlad Zhukovskyi + * @author Mykola Morhun */ @Singleton public class ChangesListPresenter implements ChangesListView.ActionDelegate { private final ChangesListView view; - private final NotificationManager notificationManager; private final ChangesPanelPresenter changesPanelPresenter; private final ComparePresenter comparePresenter; - private Project project; + private AlteredFiles alteredFiles; private String file; private String revisionA; private String revisionB; - private Status status; @Inject public ChangesListPresenter( ChangesListView view, ComparePresenter comparePresenter, - NotificationManager notificationManager, ChangesPanelPresenter changesPanelPresenter) { this.comparePresenter = comparePresenter; this.view = view; - this.notificationManager = notificationManager; + this.changesPanelPresenter = changesPanelPresenter; this.changesPanelPresenter.setFileNodeDoubleClickHandler( (path, status) -> this.onCompareClicked()); @@ -75,7 +66,6 @@ public ChangesListPresenter( } ChangesListPresenter.this.view.setEnableCompareButton(true); ChangesListPresenter.this.file = node.getName(); - ChangesListPresenter.this.status = ((ChangedFileNode) node).getStatus(); }; ChangesPanelView changesPanelView = changesPanelPresenter.getView(); @@ -84,27 +74,24 @@ public ChangesListPresenter( } /** - * Show window with changed files. + * Shows window with changed files. * - * @param changedFiles Map with files and their status + * @param alteredFiles files and their status * @param revisionA hash of the first revision or branch. If it is set to {@code null}, compare * with empty repository state will be performed * @param revisionB hash of the second revision or branch. If it is set to {@code null}, compare * with latest repository state will be performed */ public void show( - Map changedFiles, - @Nullable String revisionA, - @Nullable String revisionB, - Project project) { - this.project = project; + AlteredFiles alteredFiles, @Nullable String revisionA, @Nullable String revisionB) { + this.alteredFiles = alteredFiles; this.revisionA = revisionA; this.revisionB = revisionB; view.setEnableCompareButton(false); view.showDialog(); - changesPanelPresenter.show(changedFiles); + changesPanelPresenter.show(alteredFiles); } @Override @@ -115,21 +102,10 @@ public void onCloseClicked() { @Override public void onCompareClicked() { if (revisionB == null) { - project - .getFile(file) - .then( - file -> { - if (file.isPresent()) { - comparePresenter.showCompareWithLatest(file.get(), status, revisionA); - } - }) - .catchError( - error -> { - notificationManager.notify(error.getMessage(), FAIL, NOT_EMERGE_MODE); - }); + + comparePresenter.showCompareWithLatest(alteredFiles, file, revisionA); } else { - comparePresenter.showCompareBetweenRevisions( - Path.valueOf(file), status, revisionA, revisionB); + comparePresenter.showCompareBetweenRevisions(alteredFiles, file, revisionA, revisionB); } } } diff --git a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/changespanel/ChangesPanelPresenter.java b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/changespanel/ChangesPanelPresenter.java index 184daa71998..7409b373960 100644 --- a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/changespanel/ChangesPanelPresenter.java +++ b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/changespanel/ChangesPanelPresenter.java @@ -10,63 +10,43 @@ */ package org.eclipse.che.ide.ext.git.client.compare.changespanel; -import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.NOT_EMERGE_MODE; -import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL; import static org.eclipse.che.ide.ext.git.client.compare.changespanel.ViewMode.LIST; import static org.eclipse.che.ide.ext.git.client.compare.changespanel.ViewMode.TREE; import com.google.inject.Inject; -import java.util.Map; -import org.eclipse.che.ide.api.app.AppContext; -import org.eclipse.che.ide.api.notification.NotificationManager; import org.eclipse.che.ide.ext.git.client.GitLocalizationConstant; +import org.eclipse.che.ide.ext.git.client.compare.AlteredFiles; import org.eclipse.che.ide.ext.git.client.compare.ComparePresenter; import org.eclipse.che.ide.ext.git.client.compare.FileStatus.Status; /** - * Presenter for displaying window with list of changed files. + * Presenter for displaying list of changed files. * * @author Igor Vinokur * @author Vlad Zhukovskyi */ public class ChangesPanelPresenter implements ChangesPanelView.ActionDelegate { + private static final String REVISION = "HEAD"; private final ChangesPanelView view; private final GitLocalizationConstant locale; - private Map changedFiles; + private AlteredFiles changedFiles; private ViewMode viewMode; private FileNodeDoubleClickHandler fileNodeDoubleClickHandler; @Inject public ChangesPanelPresenter( - GitLocalizationConstant locale, - ChangesPanelView view, - AppContext appContext, - NotificationManager notificationManager, - ComparePresenter comparePresenter) { + GitLocalizationConstant locale, ChangesPanelView view, ComparePresenter comparePresenter) { + this.locale = locale; this.view = view; this.view.setDelegate(this); this.viewMode = TREE; this.fileNodeDoubleClickHandler = - (path, status) -> { - appContext - .getRootProject() - .getFile(path) - .then( - file -> { - if (file.isPresent()) { - comparePresenter.showCompareWithLatest(file.get(), status, "HEAD"); - } - }) - .catchError( - error -> { - notificationManager.notify(error.getMessage(), FAIL, NOT_EMERGE_MODE); - }); - }; + (path, status) -> comparePresenter.showCompareWithLatest(changedFiles, path, REVISION); } /** @@ -75,7 +55,7 @@ public ChangesPanelPresenter( * * @param changedFiles Map with files and their status */ - public void show(Map changedFiles) { + public void show(AlteredFiles changedFiles) { this.changedFiles = changedFiles; if (changedFiles.isEmpty()) { view.setTextToChangeViewModeButton(locale.changeListRowListViewButtonText()); diff --git a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/changespanel/ChangesPanelView.java b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/changespanel/ChangesPanelView.java index 48b7265d95f..54e42ab9432 100644 --- a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/changespanel/ChangesPanelView.java +++ b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/changespanel/ChangesPanelView.java @@ -10,9 +10,9 @@ */ package org.eclipse.che.ide.ext.git.client.compare.changespanel; -import java.util.Map; import java.util.Set; import org.eclipse.che.ide.api.mvp.View; +import org.eclipse.che.ide.ext.git.client.compare.AlteredFiles; import org.eclipse.che.ide.ext.git.client.compare.FileStatus.Status; import org.eclipse.che.ide.resource.Path; import org.eclipse.che.ide.ui.smartTree.Tree; @@ -55,7 +55,7 @@ interface ActionDelegate { /** Add selection changed handler. */ void addSelectionHandler(SelectionChangedHandler handler); - void viewChangedFiles(Map files, ViewMode viewMode); + void viewChangedFiles(AlteredFiles files, ViewMode viewMode); /** Clear panel from old nodes. */ void resetPanelState(); diff --git a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/changespanel/ChangesPanelViewImpl.java b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/changespanel/ChangesPanelViewImpl.java index 3ed438d6651..97f0af42d9d 100644 --- a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/changespanel/ChangesPanelViewImpl.java +++ b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/changespanel/ChangesPanelViewImpl.java @@ -31,6 +31,7 @@ import org.eclipse.che.ide.api.data.tree.Node; import org.eclipse.che.ide.ext.git.client.GitLocalizationConstant; import org.eclipse.che.ide.ext.git.client.GitResources; +import org.eclipse.che.ide.ext.git.client.compare.AlteredFiles; import org.eclipse.che.ide.ext.git.client.compare.FileStatus.Status; import org.eclipse.che.ide.project.shared.NodesResources; import org.eclipse.che.ide.resource.Path; @@ -101,19 +102,20 @@ public void addSelectionHandler(SelectionChangedHandler handler) { } @Override - public void viewChangedFiles(Map files, ViewMode viewMode) { + public void viewChangedFiles(AlteredFiles files, ViewMode viewMode) { NodeStorage nodeStorage = tree.getNodeStorage(); nodeStorage.clear(); if (viewMode == TREE) { - getGroupedNodes(files).forEach(nodeStorage::add); + getGroupedNodes(files.getChangedFilesMap()).forEach(nodeStorage::add); tree.expandAll(); } else { files - .keySet() + .getAlteredFilesList() .forEach( file -> nodeStorage.add( - new ChangedFileNode(file, files.get(file), nodesResources, delegate, false))); + new ChangedFileNode( + file, files.getStatusByFilePath(file), nodesResources, delegate, false))); } } diff --git a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/revisionslist/RevisionListPresenter.java b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/revisionslist/RevisionListPresenter.java index d90b81d92ab..6ae7761fca2 100644 --- a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/revisionslist/RevisionListPresenter.java +++ b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/compare/revisionslist/RevisionListPresenter.java @@ -15,7 +15,6 @@ import static org.eclipse.che.api.git.shared.DiffType.NAME_STATUS; import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.NOT_EMERGE_MODE; import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL; -import static org.eclipse.che.ide.ext.git.client.compare.FileStatus.defineStatus; import static org.eclipse.che.ide.util.ExceptionUtils.getErrorCode; import com.google.inject.Inject; @@ -23,13 +22,13 @@ import javax.validation.constraints.NotNull; import org.eclipse.che.api.core.ErrorCodes; import org.eclipse.che.api.git.shared.Revision; -import org.eclipse.che.ide.api.app.AppContext; import org.eclipse.che.ide.api.dialogs.DialogFactory; import org.eclipse.che.ide.api.git.GitServiceClient; import org.eclipse.che.ide.api.notification.NotificationManager; import org.eclipse.che.ide.api.resources.File; import org.eclipse.che.ide.api.resources.Project; import org.eclipse.che.ide.ext.git.client.GitLocalizationConstant; +import org.eclipse.che.ide.ext.git.client.compare.AlteredFiles; import org.eclipse.che.ide.ext.git.client.compare.ComparePresenter; import org.eclipse.che.ide.resource.Path; @@ -46,7 +45,6 @@ public class RevisionListPresenter implements RevisionListView.ActionDelegate { private final RevisionListView view; private final GitServiceClient service; private final GitLocalizationConstant locale; - private final AppContext appContext; private final NotificationManager notificationManager; private Revision selectedRevision; @@ -60,14 +58,14 @@ public RevisionListPresenter( GitServiceClient service, GitLocalizationConstant locale, NotificationManager notificationManager, - DialogFactory dialogFactory, - AppContext appContext) { + DialogFactory dialogFactory) { + this.view = view; this.comparePresenter = comparePresenter; this.dialogFactory = dialogFactory; this.service = service; this.locale = locale; - this.appContext = appContext; + this.notificationManager = notificationManager; this.view.setDelegate(this); @@ -165,18 +163,9 @@ private void compare() { null) .show(); } else { - appContext - .getRootProject() - .getFile(diff.substring(2)) - .then( - file -> { - if (file.isPresent()) { - comparePresenter.showCompareWithLatest( - file.get(), - defineStatus(diff.substring(0, 1)), - selectedRevision.getId()); - } - }); + AlteredFiles alteredFiles = new AlteredFiles(project, diff); + comparePresenter.showCompareWithLatest( + alteredFiles, null, selectedRevision.getId()); } }) .catchError( diff --git a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/history/HistoryPresenter.java b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/history/HistoryPresenter.java index 63b3547ef1a..f3403066a04 100644 --- a/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/history/HistoryPresenter.java +++ b/plugins/plugin-git/che-plugin-git-ext-git/src/main/java/org/eclipse/che/ide/ext/git/client/history/HistoryPresenter.java @@ -15,15 +15,12 @@ import static org.eclipse.che.api.git.shared.DiffType.NAME_STATUS; import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.EMERGE_MODE; import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL; -import static org.eclipse.che.ide.ext.git.client.compare.FileStatus.defineStatus; import static org.eclipse.che.ide.util.ExceptionUtils.getErrorCode; import com.google.inject.Inject; import com.google.inject.Singleton; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import javax.validation.constraints.NotNull; import org.eclipse.che.api.core.ErrorCodes; import org.eclipse.che.api.git.shared.Revision; @@ -33,8 +30,8 @@ import org.eclipse.che.ide.api.notification.NotificationManager; import org.eclipse.che.ide.api.resources.Project; import org.eclipse.che.ide.ext.git.client.GitLocalizationConstant; +import org.eclipse.che.ide.ext.git.client.compare.AlteredFiles; import org.eclipse.che.ide.ext.git.client.compare.ComparePresenter; -import org.eclipse.che.ide.ext.git.client.compare.FileStatus; import org.eclipse.che.ide.ext.git.client.compare.changeslist.ChangesListPresenter; import org.eclipse.che.ide.resource.Path; @@ -187,19 +184,13 @@ private void compare() { .show(); return; } - final String[] changedFiles = diff.split("\n"); - if (changedFiles.length == 1) { + AlteredFiles alteredFiles = new AlteredFiles(project, diff); + if (alteredFiles.getFilesQuantity() == 1) { comparePresenter.showCompareBetweenRevisions( - Path.valueOf(diff.substring(2)), - defineStatus(diff.substring(0, 1)), - revisionA, - revisionB); + alteredFiles, null, revisionA, revisionB); } else { - Map items = new HashMap<>(); - for (String item : changedFiles) { - items.put(item.substring(2, item.length()), defineStatus(item.substring(0, 1))); - } - changesListPresenter.show(items, revisionA, revisionB, project); + + changesListPresenter.show(alteredFiles, revisionA, revisionB); } }) .catchError( diff --git a/plugins/plugin-git/che-plugin-git-ext-git/src/main/resources/org/eclipse/che/ide/ext/git/Git.gwt.xml b/plugins/plugin-git/che-plugin-git-ext-git/src/main/resources/org/eclipse/che/ide/ext/git/Git.gwt.xml index d04e44f8873..c596b64c2b6 100644 --- a/plugins/plugin-git/che-plugin-git-ext-git/src/main/resources/org/eclipse/che/ide/ext/git/Git.gwt.xml +++ b/plugins/plugin-git/che-plugin-git-ext-git/src/main/resources/org/eclipse/che/ide/ext/git/Git.gwt.xml @@ -23,6 +23,7 @@ + - + diff --git a/plugins/plugin-git/che-plugin-git-ext-git/src/main/resources/org/eclipse/che/ide/ext/git/client/GitLocalizationConstant.properties b/plugins/plugin-git/che-plugin-git-ext-git/src/main/resources/org/eclipse/che/ide/ext/git/client/GitLocalizationConstant.properties index 5fdfd82e2f7..fd3eb4cf571 100644 --- a/plugins/plugin-git/che-plugin-git-ext-git/src/main/resources/org/eclipse/che/ide/ext/git/client/GitLocalizationConstant.properties +++ b/plugins/plugin-git/che-plugin-git-ext-git/src/main/resources/org/eclipse/che/ide/ext/git/client/GitLocalizationConstant.properties @@ -29,6 +29,9 @@ button.no=No button.fetch=Fetch button.push=Push button.pull=Pull +button.save_changes=Save Changes +button.next_diff=Next +button.previous_diff=Previous ############MESSAGES################ messages.unableGetSshKey = Failed to get private ssh key. \ diff --git a/plugins/plugin-git/che-plugin-git-ext-git/src/test/java/org/eclipse/che/ide/ext/git/client/commit/CommitPresenterTest.java b/plugins/plugin-git/che-plugin-git-ext-git/src/test/java/org/eclipse/che/ide/ext/git/client/commit/CommitPresenterTest.java index 201cfe5c5e4..ae37d6d2745 100644 --- a/plugins/plugin-git/che-plugin-git-ext-git/src/test/java/org/eclipse/che/ide/ext/git/client/commit/CommitPresenterTest.java +++ b/plugins/plugin-git/che-plugin-git-ext-git/src/test/java/org/eclipse/che/ide/ext/git/client/commit/CommitPresenterTest.java @@ -11,12 +11,9 @@ package org.eclipse.che.ide.ext.git.client.commit; import static java.util.Collections.singletonList; -import static java.util.Collections.singletonMap; import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.FLOAT_MODE; import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL; import static org.eclipse.che.ide.api.notification.StatusNotification.Status.SUCCESS; -import static org.eclipse.che.ide.ext.git.client.compare.FileStatus.Status.ADDED; -import static org.eclipse.che.ide.ext.git.client.compare.FileStatus.Status.MODIFIED; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; @@ -45,6 +42,7 @@ import org.eclipse.che.ide.commons.exception.ServerException; import org.eclipse.che.ide.ext.git.client.BaseTest; import org.eclipse.che.ide.ext.git.client.DateTimeFormatter; +import org.eclipse.che.ide.ext.git.client.compare.AlteredFiles; import org.eclipse.che.ide.ext.git.client.compare.changespanel.ChangesPanelPresenter; import org.eclipse.che.ide.resource.Path; import org.junit.Test; @@ -150,6 +148,8 @@ public void shouldShowMessageWhenNothingToCommit() throws Exception { @Test public void shouldShowDialog() throws Exception { + final String diff = "M\tfile"; + final AlteredFiles alteredFiles = new AlteredFiles(project, diff); ConfirmDialog dialog = mock(ConfirmDialog.class); when(dialogFactory.createConfirmDialog( anyString(), @@ -162,13 +162,13 @@ public void shouldShowDialog() throws Exception { presenter.showDialog(project); verify(stringPromise).then(stringCaptor.capture()); - stringCaptor.getValue().apply("M file"); + stringCaptor.getValue().apply("M\tfile"); verify(logPromise).then(logCaptor.capture()); logCaptor.getValue().apply(null); verify(view).setEnableAmendCheckBox(true); verify(view).setEnablePushAfterCommitCheckBox(true); - verify(changesPanelPresenter).show(eq(singletonMap("file", MODIFIED))); + verify(changesPanelPresenter).show(eq(alteredFiles)); verify(view).focusInMessageField(); verify(view).setEnableCommitButton(eq(DISABLE_BUTTON)); verify(view).getMessage(); @@ -178,6 +178,8 @@ public void shouldShowDialog() throws Exception { @Test public void shouldShowUntrackedFilesOnInitialCommit() throws Exception { + final String diff = "A\tfile"; + final AlteredFiles alteredFiles = new AlteredFiles(project, diff); PromiseError error = mock(PromiseError.class); ServerException exception = mock(ServerException.class); when(exception.getErrorCode()).thenReturn(ErrorCodes.INIT_COMMIT_WAS_NOT_PERFORMED); @@ -195,7 +197,7 @@ public void shouldShowUntrackedFilesOnInitialCommit() throws Exception { verify(view).setEnableAmendCheckBox(false); verify(view).setEnablePushAfterCommitCheckBox(false); - verify(changesPanelPresenter).show(eq(singletonMap("file", ADDED))); + verify(changesPanelPresenter).show(eq(alteredFiles)); verify(view).focusInMessageField(); verify(view).setEnableCommitButton(eq(DISABLE_BUTTON)); verify(view).getMessage(); @@ -209,7 +211,7 @@ public void shouldEnableCommitButton() throws Exception { presenter.showDialog(project); verify(stringPromise).then(stringCaptor.capture()); - stringCaptor.getValue().apply("M file"); + stringCaptor.getValue().apply("M\tfile"); verify(logPromise).then(logCaptor.capture()); logCaptor.getValue().apply(null); diff --git a/plugins/plugin-git/che-plugin-git-ext-git/src/test/java/org/eclipse/che/ide/ext/git/client/compare/AlteredFilesTest.java b/plugins/plugin-git/che-plugin-git-ext-git/src/test/java/org/eclipse/che/ide/ext/git/client/compare/AlteredFilesTest.java new file mode 100644 index 00000000000..1e9b96a01af --- /dev/null +++ b/plugins/plugin-git/che-plugin-git-ext-git/src/test/java/org/eclipse/che/ide/ext/git/client/compare/AlteredFilesTest.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2012-2017 Red Hat, Inc. + * 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: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.ide.ext.git.client.compare; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.util.Arrays; +import org.eclipse.che.ide.api.resources.Project; +import org.eclipse.che.ide.ext.git.client.compare.FileStatus.Status; +import org.mockito.Mock; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** @author Mykola Morhun */ +public class AlteredFilesTest { + + private static final String[] FILES = { + "Test1.java", "Test2.java", "Test3.java", "/dir/Test4.java" + }; + private static final String[] STATUSES_STRINGS = {"A", "M", "D", "M"}; + private static final Status[] STATUSES = + Arrays.stream(STATUSES_STRINGS).map(FileStatus::defineStatus).toArray(Status[]::new); + private static final int FILES_LEN = FILES.length; + + private static String diff; + + @Mock private Project project; + + private AlteredFiles alteredFiles; + + @BeforeClass + public void setupClass() { + StringBuilder diffBuilder = new StringBuilder(); + + for (int i = 0; i < FILES_LEN; i++) { + diffBuilder.append(STATUSES_STRINGS[i]).append("\t").append(FILES[i]).append("\n"); + } + diffBuilder.setLength(diffBuilder.length() - 1); + + diff = diffBuilder.toString(); + } + + @BeforeMethod + public void setup() { + alteredFiles = null; + } + + @Test + public void shouldBeAbleToCreateAlteredFilesList() { + alteredFiles = new AlteredFiles(project, diff); + + assertEquals(alteredFiles.getFilesQuantity(), FILES_LEN); + assertEquals(alteredFiles.getProject(), project); + } + + @Test + public void shouldBeAbleToCreateEmptyFilesList() { + alteredFiles = new AlteredFiles(project, ""); + + assertTrue(alteredFiles.isEmpty()); + assertEquals(alteredFiles.getFilesQuantity(), 0); + } + + @Test + public void shouldBeAbleToGetFileByIndex() { + alteredFiles = new AlteredFiles(project, diff); + + for (int i = 0; i < FILES_LEN; i++) { + assertEquals(alteredFiles.getFileByIndex(i), FILES[i]); + } + } + + @Test + public void shouldBeAbleToGetStatusByIndex() { + alteredFiles = new AlteredFiles(project, diff); + + for (int i = 0; i < FILES_LEN; i++) { + assertEquals(alteredFiles.getStatusByIndex(i), STATUSES[i]); + } + } + + @Test + public void shouldBeAbleToGetStatusByFile() { + alteredFiles = new AlteredFiles(project, diff); + + for (int i = 0; i < FILES_LEN; i++) { + assertEquals(alteredFiles.getStatusByFilePath(FILES[i]), STATUSES[i]); + } + } + + @Test(expectedExceptions = IllegalArgumentException.class, dataProvider = "invalidDiffFileData") + public void shouldThrowIllegalArgumentExceptionIfDiffFileDescriptionIsInvalid( + String invalidDiffFileData) { + alteredFiles = new AlteredFiles(project, diff + '\n' + invalidDiffFileData); + } + + @DataProvider(name = "invalidDiffFileData") + private Object[][] getInvalidDiffFileData() { + return new Object[][] {{"M "}, {"M_Test.java"}, {"Test.java"}, {" "}}; + } +} diff --git a/plugins/plugin-git/che-plugin-git-ext-git/src/test/java/org/eclipse/che/ide/ext/git/client/history/HistoryPresenterTest.java b/plugins/plugin-git/che-plugin-git-ext-git/src/test/java/org/eclipse/che/ide/ext/git/client/history/HistoryPresenterTest.java index 465c1ed6c95..ee865813692 100644 --- a/plugins/plugin-git/che-plugin-git-ext-git/src/test/java/org/eclipse/che/ide/ext/git/client/history/HistoryPresenterTest.java +++ b/plugins/plugin-git/che-plugin-git-ext-git/src/test/java/org/eclipse/che/ide/ext/git/client/history/HistoryPresenterTest.java @@ -13,13 +13,11 @@ import static java.util.Collections.singletonList; import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.EMERGE_MODE; import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL; -import static org.eclipse.che.ide.ext.git.client.compare.FileStatus.Status.MODIFIED; import static org.eclipse.che.ide.resource.Path.EMPTY; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyList; -import static org.mockito.Matchers.anyMap; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; @@ -36,10 +34,10 @@ import org.eclipse.che.api.promises.client.PromiseError; import org.eclipse.che.ide.api.dialogs.ConfirmCallback; import org.eclipse.che.ide.api.dialogs.MessageDialog; -import org.eclipse.che.ide.api.resources.Project; import org.eclipse.che.ide.api.resources.Resource; import org.eclipse.che.ide.commons.exception.ServerException; import org.eclipse.che.ide.ext.git.client.BaseTest; +import org.eclipse.che.ide.ext.git.client.compare.AlteredFiles; import org.eclipse.che.ide.ext.git.client.compare.ComparePresenter; import org.eclipse.che.ide.ext.git.client.compare.changeslist.ChangesListPresenter; import org.eclipse.che.ide.resource.Path; @@ -131,6 +129,8 @@ public void shouldShowNotificationOnGetLogError() throws Exception { @Test public void shouldShowCompareWhenOneFileChangedInCurrentRevision() throws Exception { + final String diff = "M\tfile"; + final AlteredFiles alteredFiles = new AlteredFiles(project, diff); Revision parentRevision = mock(Revision.class); Revision selectedRevision = mock(Revision.class); when(parentRevision.getId()).thenReturn("commitA"); @@ -147,15 +147,16 @@ public void shouldShowCompareWhenOneFileChangedInCurrentRevision() throws Except logCaptor.getValue().apply(logResponse); presenter.onCompareClicked(); verify(stringPromise).then(stringCaptor.capture()); - stringCaptor.getValue().apply("M file"); + stringCaptor.getValue().apply(diff); verify(comparePresenter) - .showCompareBetweenRevisions( - eq(Path.valueOf("file")), eq(MODIFIED), eq("commitA"), eq("commitB")); + .showCompareBetweenRevisions(eq(alteredFiles), anyString(), eq("commitA"), eq("commitB")); } @Test public void shouldShowChangedListWhenSeveralFilesChangedInSelectedRevision() throws Exception { + final String diff = "M\tfile1\nM\tfile2"; + final AlteredFiles alteredFiles = new AlteredFiles(project, diff); Revision revisionA = mock(Revision.class); Revision revisionB = mock(Revision.class); when(revisionA.getId()).thenReturn("commitA"); @@ -172,9 +173,9 @@ public void shouldShowChangedListWhenSeveralFilesChangedInSelectedRevision() thr logCaptor.getValue().apply(logResponse); presenter.onCompareClicked(); verify(stringPromise).then(stringCaptor.capture()); - stringCaptor.getValue().apply("M file1\nM file2"); + stringCaptor.getValue().apply(diff); - verify(changesListPresenter).show(anyMap(), eq("commitB"), eq("commitA"), any(Project.class)); + verify(changesListPresenter).show(eq(alteredFiles), eq("commitB"), eq("commitA")); } @Test diff --git a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/refactoring/preview/PreviewViewImpl.java b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/refactoring/preview/PreviewViewImpl.java index a2a459e8541..777208e1f08 100644 --- a/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/refactoring/preview/PreviewViewImpl.java +++ b/plugins/plugin-java/che-plugin-java-ext-lang-client/src/main/java/org/eclipse/che/ide/ext/java/client/refactoring/preview/PreviewViewImpl.java @@ -13,8 +13,10 @@ import static com.google.gwt.dom.client.Style.Float.LEFT; import static com.google.gwt.dom.client.Style.Unit.PX; import static org.eclipse.che.ide.api.theme.Style.getEditorSelectionColor; +import static org.eclipse.che.ide.orion.compare.CompareInitializer.GIT_COMPARE_MODULE; import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.event.dom.client.ClickEvent; @@ -25,6 +27,7 @@ import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.FlowPanel; @@ -45,7 +48,6 @@ import javax.validation.constraints.NotNull; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.ide.api.theme.Style; -import org.eclipse.che.ide.api.theme.ThemeAgent; import org.eclipse.che.ide.ext.java.client.JavaLocalizationConstant; import org.eclipse.che.ide.ext.java.shared.dto.refactoring.ChangePreview; import org.eclipse.che.ide.ext.java.shared.dto.refactoring.RefactoringPreview; @@ -53,10 +55,11 @@ import org.eclipse.che.ide.ext.java.shared.dto.refactoring.RefactoringStatusEntry; import org.eclipse.che.ide.orion.compare.CompareConfig; import org.eclipse.che.ide.orion.compare.CompareFactory; -import org.eclipse.che.ide.orion.compare.CompareWidget; +import org.eclipse.che.ide.orion.compare.CompareInitializer; import org.eclipse.che.ide.orion.compare.FileOptions; -import org.eclipse.che.ide.ui.loaders.request.LoaderFactory; +import org.eclipse.che.ide.orion.compare.jso.GitCompareOverlay; import org.eclipse.che.ide.ui.window.Window; +import org.eclipse.che.requirejs.ModuleHolder; /** * @author Dmitry Shnurenko @@ -82,11 +85,11 @@ interface PreviewViewImplUiBinder extends UiBinder {} private ActionDelegate delegate; private FileOptions newFile; private FileOptions oldFile; - private CompareWidget compare; - private ThemeAgent themeAgent; + private GitCompareOverlay compare; - private final LoaderFactory loaderFactory; private final CompareFactory compareFactory; + private final CompareInitializer compareInitializer; + private final ModuleHolder moduleHolder; private Map containerChanges = new HashMap<>(); private Element selectedElement; @@ -94,13 +97,13 @@ interface PreviewViewImplUiBinder extends UiBinder {} @Inject public PreviewViewImpl( JavaLocalizationConstant locale, - LoaderFactory loaderFactory, CompareFactory compareFactory, - ThemeAgent themeAgent) { + CompareInitializer compareInitializer, + ModuleHolder moduleHolder) { this.locale = locale; - this.loaderFactory = loaderFactory; this.compareFactory = compareFactory; - this.themeAgent = themeAgent; + this.compareInitializer = compareInitializer; + this.moduleHolder = moduleHolder; setTitle(locale.moveDialogTitle()); @@ -338,9 +341,8 @@ public void showDiff(@Nullable ChangePreview preview) { prepareDiffEditor(preview); return; } - refreshComperingFiles(preview); - compare.reload(); + refreshComperingFiles(preview); } } @@ -349,6 +351,8 @@ private void refreshComperingFiles(@NotNull ChangePreview preview) { newFile.setName(preview.getFileName()); oldFile.setContent(preview.getOldContent()); oldFile.setName(preview.getFileName()); + + compare.update(newFile, oldFile); } private void prepareDiffEditor(@NotNull ChangePreview preview) { @@ -366,8 +370,18 @@ private void prepareDiffEditor(@NotNull ChangePreview preview) { compareConfig.setShowTitle(true); compareConfig.setShowLineStatus(true); - compare = new CompareWidget(compareConfig, themeAgent.getCurrentThemeId(), loaderFactory); - diff.add(compare); + compareInitializer.injectCompareWidget( + new AsyncCallback() { + @Override + public void onSuccess(Void result) { + JavaScriptObject gitCompare = moduleHolder.getModule(GIT_COMPARE_MODULE); + compare = + GitCompareOverlay.create(gitCompare, compareConfig, diff.getElement().getId()); + } + + @Override + public void onFailure(Throwable caught) {} + }); } private void showMessage(RefactoringStatus status) { diff --git a/plugins/plugin-orion/che-plugin-orion-compare/pom.xml b/plugins/plugin-orion/che-plugin-orion-compare/pom.xml index 2e580d8548b..d362978a5b2 100644 --- a/plugins/plugin-orion/che-plugin-orion-compare/pom.xml +++ b/plugins/plugin-orion/che-plugin-orion-compare/pom.xml @@ -25,10 +25,6 @@ false - - com.google.gwt - gwt-elemental - com.google.gwt gwt-user diff --git a/plugins/plugin-orion/che-plugin-orion-compare/src/main/java/org/eclipse/che/ide/orion/compare/CompareInitializer.java b/plugins/plugin-orion/che-plugin-orion-compare/src/main/java/org/eclipse/che/ide/orion/compare/CompareInitializer.java new file mode 100644 index 00000000000..4887e6c08ac --- /dev/null +++ b/plugins/plugin-orion/che-plugin-orion-compare/src/main/java/org/eclipse/che/ide/orion/compare/CompareInitializer.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2012-2017 Red Hat, Inc. + * 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: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.ide.orion.compare; + +import com.google.gwt.core.client.Callback; +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import org.eclipse.che.api.promises.client.Promise; +import org.eclipse.che.api.promises.client.callback.AsyncPromiseHelper; +import org.eclipse.che.ide.api.theme.ThemeAgent; +import org.eclipse.che.requirejs.RequireJsLoader; + +/** @author Mykola Morhun */ +@Singleton +public class CompareInitializer { + + public static final String GIT_COMPARE_MODULE = "Compare"; + + private final RequireJsLoader requireJsLoader; + private final ThemeAgent themeAgent; + + @Inject + CompareInitializer(final RequireJsLoader requireJsLoader, final ThemeAgent themeAgent) { + this.requireJsLoader = requireJsLoader; + this.themeAgent = themeAgent; + } + + public Promise injectCompareWidget(final AsyncCallback callback) { + loadCompareTheme(); + return AsyncPromiseHelper.createFromAsyncRequest( + call -> + requireJsLoader.require( + new Callback() { + @Override + public void onFailure(Throwable reason) { + callback.onFailure(reason); + } + + @Override + public void onSuccess(JavaScriptObject[] result) { + callback.onSuccess(null); + } + }, + new String[] {"built-compare/built-compare-amd.min"}, + new String[] {GIT_COMPARE_MODULE})); + } + + /** + * Dynamically loads theme for compare widget. This is done here to not to load big css when user + * doesn't need it. + */ + private void loadCompareTheme() { + String themeUrl = + GWT.getModuleBaseURL() + + '/' + + themeAgent.getCurrentThemeId() + + "-built-compare-codenvy.css"; + + Element styleSheetLink = Document.get().createElement("link"); + styleSheetLink.setAttribute("rel", "stylesheet"); + styleSheetLink.setAttribute("href", themeUrl); + Document.get().getElementsByTagName("head").getItem(0).appendChild(styleSheetLink); + } +} diff --git a/plugins/plugin-orion/che-plugin-orion-compare/src/main/java/org/eclipse/che/ide/orion/compare/CompareWidget.java b/plugins/plugin-orion/che-plugin-orion-compare/src/main/java/org/eclipse/che/ide/orion/compare/CompareWidget.java deleted file mode 100644 index ef0dfb971bf..00000000000 --- a/plugins/plugin-orion/che-plugin-orion-compare/src/main/java/org/eclipse/che/ide/orion/compare/CompareWidget.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (c) 2012-2017 Red Hat, Inc. - * 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: - * Red Hat, Inc. - initial API and implementation - */ -package org.eclipse.che.ide.orion.compare; - -import static org.eclipse.che.api.promises.client.callback.AsyncPromiseHelper.createFromAsyncRequest; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.Style; -import com.google.gwt.event.dom.client.LoadEvent; -import com.google.gwt.event.dom.client.LoadHandler; -import com.google.gwt.json.client.JSONObject; -import com.google.gwt.json.client.JSONString; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.Frame; -import elemental.events.CustomEvent; -import elemental.events.Event; -import elemental.events.EventListener; -import elemental.html.Window; -import elemental.js.html.JsIFrameElement; -import elemental.json.Json; -import org.eclipse.che.api.promises.client.Operation; -import org.eclipse.che.api.promises.client.OperationException; -import org.eclipse.che.api.promises.client.Promise; -import org.eclipse.che.api.promises.client.callback.AsyncPromiseHelper; -import org.eclipse.che.ide.ui.loaders.request.LoaderFactory; -import org.eclipse.che.ide.ui.loaders.request.MessageLoader; - -/** - * Widget for compering (diff) for two files. - * - * @author Evgen Vidolob - * @author Igor Vinokur - */ -public class CompareWidget extends Composite { - - private Frame frame; - private JsIFrameElement iFrame; - private Promise framePromise; - private CompareConfig compareConfig; - - /** Callback for receiving content. */ - public interface ContentCallBack { - void onContentReceived(String content); - } - - public CompareWidget( - final CompareConfig compareConfig, final String themeId, LoaderFactory loaderFactory) { - this.compareConfig = compareConfig; - this.frame = new Frame(GWT.getModuleBaseURL() + "/Compare.html"); - initWidget(frame); - setSize("100%", "100%"); - final MessageLoader loader = loaderFactory.newLoader(); - loader.show(); - frame.getElement().getStyle().setBorderStyle(Style.BorderStyle.NONE); - AsyncPromiseHelper.RequestCall call = - new AsyncPromiseHelper.RequestCall() { - @Override - public void makeCall(final AsyncCallback callback) { - frame.addLoadHandler( - new LoadHandler() { - @Override - public void onLoad(LoadEvent event) { - frame.getElement().cast(); - iFrame = frame.getElement().cast(); - callback.onSuccess(iFrame.getContentWindow()); - } - }); - } - }; - framePromise = createFromAsyncRequest(call); - framePromise.then( - arg -> { - sendThemeId(arg, themeId); - }); - framePromise.then( - arg -> { - final EventListener eventListener = - evt -> { - sendConfig(arg, compareConfig); - loader.hide(); - }; - arg.getDocument().addEventListener("onThemeLoaded", eventListener, false); - }); - } - - private void sendThemeId(Window arg, String themeId) { - JSONObject obj = new JSONObject(); - obj.put("type", new JSONString("theme")); - obj.put("message", new JSONString(themeId)); - arg.postMessage(obj.toString(), "*"); - } - - private void sendConfig(Window arg, CompareConfig compareConfig) { - String message = - "{\"type\": \"config\", \"message\":" + Json.create(compareConfig.toJson()).toJson() + "}"; - arg.postMessage(message, "*"); - } - - /** Reload compare according to configuration. */ - public void reload() { - framePromise.then( - new Operation() { - @Override - public void apply(Window arg) throws OperationException { - sendConfig(arg, compareConfig); - } - }); - } - - /** Refresh compare parts. */ - public void refresh() { - framePromise.then( - new Operation() { - @Override - public void apply(Window arg) throws OperationException { - JSONObject obj = new JSONObject(); - obj.put("type", new JSONString("refresh")); - arg.postMessage(obj.toString(), "*"); - } - }); - } - - /** - * get content of editable part of compare widget. - * - * @param contentCallBack callBack with received contents - */ - public void getContent(final ContentCallBack contentCallBack) { - framePromise.then( - new Operation() { - @Override - public void apply(final Window arg) throws OperationException { - JSONObject obj = new JSONObject(); - obj.put("type", new JSONString("getNewContentRequest")); - arg.postMessage(obj.toString(), "*"); - - arg.addEventListener( - "getNewContentResponse", - new EventListener() { - @Override - public void handleEvent(Event evt) { - contentCallBack.onContentReceived(((CustomEvent) evt).getDetail().toString()); - } - }, - false); - } - }); - } -} diff --git a/plugins/plugin-orion/che-plugin-orion-compare/src/main/java/org/eclipse/che/ide/orion/compare/jso/GitCompareOverlay.java b/plugins/plugin-orion/che-plugin-orion-compare/src/main/java/org/eclipse/che/ide/orion/compare/jso/GitCompareOverlay.java new file mode 100644 index 00000000000..71265a991a0 --- /dev/null +++ b/plugins/plugin-orion/che-plugin-orion-compare/src/main/java/org/eclipse/che/ide/orion/compare/jso/GitCompareOverlay.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2012-2017 Red Hat, Inc. + * 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: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.ide.orion.compare.jso; + +import com.google.gwt.core.client.JavaScriptObject; +import org.eclipse.che.ide.orion.compare.CompareConfig; +import org.eclipse.che.ide.orion.compare.FileOptions; + +/** @author Mykola Morhun */ +public class GitCompareOverlay extends JavaScriptObject { + + protected GitCompareOverlay() {} + + public static native GitCompareOverlay create( + JavaScriptObject gitCompareJso, CompareConfig compareConfig, String parentId) /*-{ + compareConfig.parentDivId = parentId; + + var compare = new gitCompareJso(compareConfig); + this.widget = compare.getCompareView().getWidget(); + + return compare; + }-*/; + + public final native void update(FileOptions newFile, FileOptions oldFile) /*-{ + var widget = this.getCompareView().getWidget(); + widget.options.newFile = newFile; + widget.options.oldFile = oldFile; + widget.refresh(); + }-*/; + + public final native void refresh() /*-{ + var widget = this.getCompareView().getWidget(); + var editors = widget.getEditors(); + var oldContent= editors[0].getTextView().getText(); + var newContent = editors[1].getTextView().getText(); + + widget.options.oldFile.Content = oldContent; + widget.options.newFile.Content = newContent; + widget.refresh(); + }-*/; + + public final native String getContent() /*-{ + return this.getCompareView().getWidget().getEditors()[1].getTextView().getText(); + }-*/; +}