From ec811c09c1b533717a6f4fa790585c9a63479a9b Mon Sep 17 00:00:00 2001 From: Stefan Rademacher Date: Mon, 22 May 2017 08:52:05 +0200 Subject: [PATCH] Add ICommitMessageProvider2 for caret positioning in commit messages The caret in the CommitMessageComponent is placed according to the value of getDesiredCaretPosition() of the first implementation of ICommitMessageProvider2 that provides caret position. In case there are multiple implementations available that provide a defined caret position the second and subsequent positions are ignored. Commit messages are not trimmed anymore to ensure consistency between a provided message and the provided caret position. Includes unit and UI tests. CQ: 13790 Bug: 516867 Change-Id: I1b0ffd33ef37196d53077d09774672c2b033835f Signed-off-by: Stefan Rademacher Signed-off-by: Thomas Wolf --- org.eclipse.egit.ui.test/fragment.xml | 9 + .../eclipse/egit/ui/common/StagingViewTester.java | 11 +- .../internal/dialogs/CommitMessageBuilderTest.java | 495 +++++++++++++++++++++ .../dialogs/CommitMessageComponentTest.java | 280 +++--------- .../test/stagview/AbstractStagingViewTestCase.java | 69 +++ .../test/stagview/CommitMessageProvidersTest.java | 123 +++++ .../egit/ui/test/stagview/StagingViewTest.java | 55 +-- .../TestCommitMessageProviderExtensionFactory.java | 68 +++ .../schema/commitMessageProvider.exsd | 6 +- .../src/org/eclipse/egit/ui/Activator.java | 12 + .../egit/ui/CommitMessageWithCaretPosition.java | 77 ++++ .../eclipse/egit/ui/ICommitMessageProvider2.java | 40 ++ .../src/org/eclipse/egit/ui/internal/UIText.java | 6 + .../ui/internal/dialogs/CommitMessageBuilder.java | 225 ++++++++++ .../internal/dialogs/CommitMessageComponent.java | 151 +++---- .../dialogs/CommitMessageComponentState.java | 19 + .../CommitMessageComponentStateManager.java | 21 +- .../egit/ui/internal/staging/StagingView.java | 7 +- .../org/eclipse/egit/ui/internal/uitext.properties | 2 + 19 files changed, 1300 insertions(+), 376 deletions(-) create mode 100644 org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageBuilderTest.java rewrite org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageComponentTest.java (69%) create mode 100644 org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/test/stagview/AbstractStagingViewTestCase.java create mode 100644 org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/test/stagview/CommitMessageProvidersTest.java create mode 100644 org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/test/stagview/TestCommitMessageProviderExtensionFactory.java create mode 100644 org.eclipse.egit.ui/src/org/eclipse/egit/ui/CommitMessageWithCaretPosition.java create mode 100644 org.eclipse.egit.ui/src/org/eclipse/egit/ui/ICommitMessageProvider2.java create mode 100644 org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageBuilder.java diff --git a/org.eclipse.egit.ui.test/fragment.xml b/org.eclipse.egit.ui.test/fragment.xml index 71b9b1de3..1ea423136 100644 --- a/org.eclipse.egit.ui.test/fragment.xml +++ b/org.eclipse.egit.ui.test/fragment.xml @@ -66,5 +66,14 @@ + + + + + + diff --git a/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/common/StagingViewTester.java b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/common/StagingViewTester.java index e7ce95c0d..665a9fbda 100644 --- a/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/common/StagingViewTester.java +++ b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/common/StagingViewTester.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2011, 2014 Jens Baumgart and others. + * Copyright (C) 2011, 2017 Jens Baumgart and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,6 +8,7 @@ *******************************************************************************/ package org.eclipse.egit.ui.common; +import static org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable.syncExec; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -22,6 +23,7 @@ import org.eclipse.egit.ui.test.ContextMenuHelper; import org.eclipse.egit.ui.test.JobJoiner; import org.eclipse.egit.ui.test.TestUtil; import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotView; +import org.eclipse.swtbot.swt.finder.widgets.SWTBotStyledText; import org.eclipse.swtbot.swt.finder.widgets.SWTBotToolbarToggleButton; import org.eclipse.swtbot.swt.finder.widgets.SWTBotTree; @@ -152,4 +154,11 @@ public class StagingViewTester { .styledTextWithLabel(UIText.StagingView_CommitMessage) .getText(); } + + public int getCaretPosition() { + SWTBotStyledText commitMessageArea = stagingView.bot().styledTextWithLabel(UIText.StagingView_CommitMessage); + Integer pos = syncExec(() -> Integer + .valueOf(commitMessageArea.widget.getCaretOffset())); + return pos == null ? -1 : pos.intValue(); + } } diff --git a/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageBuilderTest.java b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageBuilderTest.java new file mode 100644 index 000000000..875f53a71 --- /dev/null +++ b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageBuilderTest.java @@ -0,0 +1,495 @@ +/******************************************************************************* + * Copyright (C) 2017, Stefan Rademacher + * + * 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 + *******************************************************************************/ +package org.eclipse.egit.ui.internal.dialogs; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.eclipse.core.resources.IResource; +import org.eclipse.egit.core.test.GitTestCase; +import org.eclipse.egit.core.test.TestRepository; +import org.eclipse.egit.ui.CommitMessageWithCaretPosition; +import org.eclipse.egit.ui.ICommitMessageProvider; +import org.eclipse.egit.ui.ICommitMessageProvider2; +import org.eclipse.jgit.lib.Constants; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class CommitMessageBuilderTest extends GitTestCase { + + TestRepository testRepository; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + gitDir = new File(project.getProject().getLocationURI().getPath(), + Constants.DOT_GIT); + testRepository = new TestRepository(gitDir); + testRepository.connect(project.getProject()); + } + + @Override + @After + public void tearDown() throws Exception { + testRepository.dispose(); + super.tearDown(); + } + + @Test + public void commitMessageProvider_noProvider() throws Exception { + CommitMessageBuilder commitMessageBuilder = newCommitMessageBuilder( + createProviderList()); + + CommitMessageWithCaretPosition commitMessageWithPosition = commitMessageBuilder + .build(); + + assertEquals("", commitMessageWithPosition.getMessage()); + } + + @Test + public void commitMessageProvider_oneProvider() throws Exception { + String message = "example single-line commit message"; + + CommitMessageBuilder commitMessageBuilder = newCommitMessageBuilder( + createProviderList(message)); + + CommitMessageWithCaretPosition commitMessageWithPosition = commitMessageBuilder + .build(); + + assertEquals(message, commitMessageWithPosition.getMessage()); + } + + @Test + public void commitMessageProvider_twoProviders() throws Exception { + String message1 = "example single-line commit message"; + String message2 = "example multi-line\n\ncommit message"; + + CommitMessageBuilder commitMessageBuilder = newCommitMessageBuilder( + createProviderList(message1, message2)); + + CommitMessageWithCaretPosition commitMessageWithPosition = commitMessageBuilder + .build(); + + assertEquals(message1 + "\n\n" + message2, + commitMessageWithPosition.getMessage()); + } + + @Test + public void commitMessageProvider_oneCrashingProvider() throws Exception { + + CommitMessageBuilder commitMessageBuilder = newCommitMessageBuilder( + Arrays.asList(new CrashingCommitMessageProvider())); + + CommitMessageWithCaretPosition commitMessageWithPosition = commitMessageBuilder + .build(); + + assertEquals("", commitMessageWithPosition.getMessage()); + } + + @Test + public void commitMessageProvider_oneCrashingProviderWithCaretPosition() { + + CommitMessageBuilder commitMessageBuilder = newCommitMessageBuilder( + Arrays.asList( + new CrashingCommitMessageProviderWithCaretPositioning())); + + CommitMessageWithCaretPosition commitMessageWithPosition = commitMessageBuilder + .build(); + + assertEquals("", commitMessageWithPosition.getMessage()); + assertEquals(CommitMessageComponentState.CARET_DEFAULT_POSITION, + commitMessageWithPosition.getDesiredCaretPosition()); + } + + @Test + public void commitMessageProvider_twoProvidersSecondOneCrashing() + throws Exception { + String message = "example single-line commit message"; + List providers = createProviderList(message); + providers.add(new CrashingCommitMessageProvider()); + + CommitMessageBuilder commitMessageBuilder = newCommitMessageBuilder( + providers); + + CommitMessageWithCaretPosition commitMessageWithPosition = commitMessageBuilder + .build(); + + assertEquals(message, commitMessageWithPosition.getMessage()); + } + + @Test + public void commitMessageProvider_twoProvidersFirstOneCrashing() + throws Exception { + String message = "example single-line commit message"; + List providers = createProviderList(message); + providers.add(0, new CrashingCommitMessageProvider()); + + CommitMessageBuilder commitMessageBuilder = newCommitMessageBuilder( + providers); + + CommitMessageWithCaretPosition commitMessageWithPosition = commitMessageBuilder + .build(); + + assertEquals(message, commitMessageWithPosition.getMessage()); + } + + @Test + public void commitMessageProvider_multipleProvidersWithCrashAndNull() + throws Exception { + String message1 = "\nexample commit message"; + String multiLineMessage = "example\nmulti-line\n\ncommit message\n\n\n"; + List providers = createProviderList( + multiLineMessage, null, message1); + providers.add(0, new CrashingCommitMessageProvider()); + providers.add(3, new CrashingCommitMessageProvider()); + + CommitMessageBuilder commitMessageBuilder = newCommitMessageBuilder( + providers); + + CommitMessageWithCaretPosition commitMessageWithPosition = commitMessageBuilder + .build(); + + assertEquals(multiLineMessage + "\n\n" + message1, + commitMessageWithPosition.getMessage()); + } + + @Test + public void commitMessageProvider_oneProviderWithCaretPositioning() { + String message = "Description: \n\nExample\nmulti-line\n\ncommit message"; + int caretPosition = 13; + CommitMessageWithCaretPosition commitMessageWithPosition = getCommitMessageWithCaretPosition( + message, caretPosition); + + assertEquals(caretPosition, + commitMessageWithPosition.getDesiredCaretPosition()); + } + + @Test + public void commitMessageProvider_oneProviderWithCaretPositionEqualsMessageLength() { + String message = "Description: "; + int caretPosition = message.length(); + CommitMessageWithCaretPosition commitMessageWithPosition = getCommitMessageWithCaretPosition( + message, caretPosition); + + assertEquals(caretPosition, + commitMessageWithPosition.getDesiredCaretPosition()); + } + + @Test + public void commitMessageProvider_oneProviderWithInvalidNegativeCaretPosition() { + String message = "Description: "; + CommitMessageWithCaretPosition commitMessageWithPosition = getCommitMessageWithCaretPosition( + message, -42); + + assertEquals(0, + commitMessageWithPosition.getDesiredCaretPosition()); + } + + @Test + public void commitMessageProvider_oneProviderWithInvalidCaretPositionExceedingMessageLength() { + String message = "Description: "; + CommitMessageWithCaretPosition commitMessageWithPosition = getCommitMessageWithCaretPosition( + message, message.length() + 1); + + assertEquals(0, commitMessageWithPosition.getDesiredCaretPosition()); + } + + private CommitMessageWithCaretPosition getCommitMessageWithCaretPosition( + String message, int caretPosition) { + ICommitMessageProvider2 providerWithCaretPositioning = createProviderWithCaretPositioning( + message, caretPosition); + + List providers = new ArrayList<>(); + providers.add(providerWithCaretPositioning); + + CommitMessageBuilder commitMessageBuilder = newCommitMessageBuilder( + providers); + return commitMessageBuilder + .build(); + } + + @Test + public void commitMessageProvider_twoProvidersFirstWithCaretPositioning() { + String singleLineMessage = "Descr.: "; + int caretPositionInSingleLineMessage = 8; + String multiLineMessage = "Description: \n\nExample\nmulti-line\n\ncommit message"; + + ICommitMessageProvider2 firstProviderWithCaretPositioning = createProviderWithCaretPositioning( + singleLineMessage, caretPositionInSingleLineMessage); + + List providers = createProviderList( + multiLineMessage); + providers.add(0, firstProviderWithCaretPositioning); + + CommitMessageBuilder commitMessageBuilder = newCommitMessageBuilder( + providers); + CommitMessageWithCaretPosition commitMessageWithPosition = commitMessageBuilder + .build(); + + assertEquals(caretPositionInSingleLineMessage, + commitMessageWithPosition.getDesiredCaretPosition()); + } + + @Test + public void commitMessageProvider_twoProvidersSecondWithCaretPositioning() { + String singleLineMessage = "Descr.: "; + String multiLineMessage = "Description: \n\nExample\nmulti-line\n\ncommit message"; + int caretPositionInMultiLineMessage = 13; + + ICommitMessageProvider2 secondProviderWithCaretPositioning = createProviderWithCaretPositioning( + multiLineMessage, caretPositionInMultiLineMessage); + + List providers = createProviderList( + singleLineMessage); + providers.add(secondProviderWithCaretPositioning); + + CommitMessageBuilder commitMessageBuilder = newCommitMessageBuilder( + providers); + CommitMessageWithCaretPosition commitMessageWithPosition = commitMessageBuilder + .build(); + + assertEquals( + singleLineMessage.length() + "\n\n".length() + + caretPositionInMultiLineMessage, + commitMessageWithPosition.getDesiredCaretPosition()); + } + + @Test + public void commitMessageProvider_twoProvidersSecondWithCaretPositionZero() { + String singleLineMessage = "Descr.: "; + String multiLineMessage = "Description: \n\nExample\nmulti-line\n\ncommit message"; + int caretPositionInMultiLineMessage = 0; + + ICommitMessageProvider2 secondProviderWithUndefinedCaretPositioning = createProviderWithCaretPositioning( + multiLineMessage, caretPositionInMultiLineMessage); + + List providers = createProviderList( + singleLineMessage); + providers.add(secondProviderWithUndefinedCaretPositioning); + + CommitMessageBuilder commitMessageBuilder = newCommitMessageBuilder( + providers); + CommitMessageWithCaretPosition commitMessageWithPosition = commitMessageBuilder + .build(); + + assertEquals( + singleLineMessage.length() + "\n\n".length() + + caretPositionInMultiLineMessage, + commitMessageWithPosition.getDesiredCaretPosition()); + } + + @Test + public void commitMessageProvider_twoProvidersSecondWithUndefinedCaretPosition() { + String singleLineMessage = "Descr.: "; + String multiLineMessage = "Description: \n\nExample\nmulti-line\n\ncommit message"; + int caretPositionInMultiLineMessage = CommitMessageWithCaretPosition.NO_POSITION; + + ICommitMessageProvider2 secondProviderWithUndefinedCaretPositioning = createProviderWithCaretPositioning( + multiLineMessage, caretPositionInMultiLineMessage); + + List providers = createProviderList( + singleLineMessage); + providers.add(secondProviderWithUndefinedCaretPositioning); + + CommitMessageBuilder commitMessageBuilder = newCommitMessageBuilder( + providers); + CommitMessageWithCaretPosition commitMessageWithPosition = commitMessageBuilder + .build(); + + assertEquals(CommitMessageComponentState.CARET_DEFAULT_POSITION, + commitMessageWithPosition.getDesiredCaretPosition()); + } + + @Test + public void commitMessageProvider_twoProvidersWithCaretPositioning() { + String singleLineMessage = "Descr.: "; + int caretPositionInSingleLineMessage = 8; + String multiLineMessage = "Description: \n\nExample\nmulti-line\n\ncommit message"; + int caretPositionInMultiLineMessage = 13; + + ICommitMessageProvider2 firstProviderWithCaretPositioning = createProviderWithCaretPositioning( + singleLineMessage, caretPositionInSingleLineMessage); + ICommitMessageProvider2 secondProviderWithCaretPositioning = createProviderWithCaretPositioning( + multiLineMessage, caretPositionInMultiLineMessage); + + List providers = new ArrayList<>(); + providers.add(firstProviderWithCaretPositioning); + providers.add(secondProviderWithCaretPositioning); + + CommitMessageBuilder commitMessageBuilder = newCommitMessageBuilder( + providers); + CommitMessageWithCaretPosition commitMessageWithPosition = commitMessageBuilder + .build(); + + assertEquals(caretPositionInSingleLineMessage, + commitMessageWithPosition.getDesiredCaretPosition()); + } + + @Test + public void commitMessageProvider_twoProvidersWithCaretPositioningFirstWithUndefinedCaretPosition() { + String singleLineMessage = "Descr.: "; + int caretPositionInSingleLineMessage = CommitMessageWithCaretPosition.NO_POSITION; + String multiLineMessage = "Description: \n\nExample\nmulti-line\n\ncommit message"; + int caretPositionInMultiLineMessage = 13; + + ICommitMessageProvider2 firstProviderWithCaretPositioning = createProviderWithCaretPositioning( + singleLineMessage, caretPositionInSingleLineMessage); + ICommitMessageProvider2 secondProviderWithCaretPositioning = createProviderWithCaretPositioning( + multiLineMessage, caretPositionInMultiLineMessage); + + List providers = new ArrayList<>(); + providers.add(firstProviderWithCaretPositioning); + providers.add(secondProviderWithCaretPositioning); + + CommitMessageBuilder commitMessageBuilder = newCommitMessageBuilder( + providers); + CommitMessageWithCaretPosition commitMessageWithPosition = commitMessageBuilder + .build(); + + assertEquals( + singleLineMessage.length() + "\n\n".length() + + caretPositionInMultiLineMessage, + commitMessageWithPosition.getDesiredCaretPosition()); + } + + @Test + public void commitMessageProvider_twoProvidersWithCaretPositioningSecondWithUndefinedCaretPosition() { + String singleLineMessage = "Descr.: "; + int caretPositionInSingleLineMessage = 8; + String multiLineMessage = "Description: \n\nExample\nmulti-line\n\ncommit message"; + int caretPositionInMultiLineMessage = CommitMessageWithCaretPosition.NO_POSITION; + + ICommitMessageProvider2 firstProviderWithCaretPositioning = createProviderWithCaretPositioning( + singleLineMessage, caretPositionInSingleLineMessage); + ICommitMessageProvider2 secondProviderWithCaretPositioning = createProviderWithCaretPositioning( + multiLineMessage, caretPositionInMultiLineMessage); + + List providers = new ArrayList<>(); + providers.add(firstProviderWithCaretPositioning); + providers.add(secondProviderWithCaretPositioning); + + CommitMessageBuilder commitMessageBuilder = newCommitMessageBuilder( + providers); + CommitMessageWithCaretPosition commitMessageWithPosition = commitMessageBuilder + .build(); + + assertEquals(caretPositionInSingleLineMessage, + commitMessageWithPosition.getDesiredCaretPosition()); + } + + @Test + public void commitMessageProvider_multipleProvidersWithCrashingAndNullAndOneCaretPositioning() { + String multiLineMessage = "Description: \n\nExample\nmulti-line\n\ncommit message"; + int caretPositionInMultiLineMessage = 13; + String singleLineMessage = "example single-line commit message"; + + ICommitMessageProvider2 providerWithCaretPositioning = createProviderWithCaretPositioning( + multiLineMessage, caretPositionInMultiLineMessage); + + List providers = createProviderList(null, + singleLineMessage); + providers.add(0, new CrashingCommitMessageProvider()); + providers.add(0, providerWithCaretPositioning); + providers.add(3, new CrashingCommitMessageProvider()); + + CommitMessageBuilder commitMessageBuilder = newCommitMessageBuilder( + providers); + + CommitMessageWithCaretPosition commitMessageWithPosition = commitMessageBuilder + .build(); + + assertEquals(multiLineMessage + "\n\n" + singleLineMessage, + commitMessageWithPosition.getMessage()); + } + + private CommitMessageBuilder newCommitMessageBuilder( + List providers) { + // Create anonymous subclass, as mocking does not currently work. + // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=349164 + return new CommitMessageBuilder(testRepository.getRepository(), + Collections.emptyList()) { + + @Override + List getCommitMessageProviders() { + return providers; + } + }; + } + + private List createProviderList( + String... messages) { + List providerList = new ArrayList<>(); + + for (String message : messages) { + providerList.add(new ICommitMessageProvider() { + + @Override + public String getMessage(IResource[] resources) { + return message; + } + }); + } + + return providerList; + } + + private ICommitMessageProvider2 createProviderWithCaretPositioning( + String message, int caretPosition) { + return new ICommitMessageProvider2() { + + @Override + public String getMessage(IResource[] resources) { + return message; + } + + @Override + public CommitMessageWithCaretPosition getCommitMessageWithPosition( + IResource[] resources) { + return new CommitMessageWithCaretPosition(message, + caretPosition); + } + }; + } + + private static class CrashingCommitMessageProvider + implements ICommitMessageProvider { + + @Override + public String getMessage(IResource[] resources) { + throw new IllegalStateException( + "CrashingCommitMessageProvider fails on purpose."); + } + + } + + private static class CrashingCommitMessageProviderWithCaretPositioning + implements ICommitMessageProvider2 { + + @Override + public String getMessage(IResource[] resources) { + return "getMessage() is not supposed to be called."; + } + + @Override + public CommitMessageWithCaretPosition getCommitMessageWithPosition( + IResource[] resources) { + throw new IllegalStateException( + "CrashingCommitMessageProviderWithCaretPositioning fails on purpose."); + } + + } + +} diff --git a/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageComponentTest.java b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageComponentTest.java dissimilarity index 69% index 5c12c1b11..7119ab281 100644 --- a/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageComponentTest.java +++ b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageComponentTest.java @@ -1,215 +1,65 @@ -/******************************************************************************* - * Copyright (C) 2015 SAP SE (Christian Georgi ) - * - * 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 - *******************************************************************************/ -package org.eclipse.egit.ui.internal.dialogs; - -import static org.junit.Assert.assertEquals; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.eclipse.core.resources.IResource; -import org.eclipse.egit.ui.ICommitMessageProvider; -import org.eclipse.egit.ui.internal.UIText; -import org.junit.Test; - -public class CommitMessageComponentTest { - - @Test - public void commitFormat_simple() { - String commitMessage = "Simple message"; - - String formattedMessage = CommitMessageComponent - .formatIssuesInCommitMessage(commitMessage); - assertEquals(null, formattedMessage); - } - - @Test - public void commitFormat_trailingWhitespace_ok() { - String commitMessage = "Simple message\n\n\n"; - - String formattedMessage = CommitMessageComponent - .formatIssuesInCommitMessage(commitMessage); - assertEquals(null, formattedMessage); - } - - @Test - public void commitFormat_MultipleLines_ok() { - String commitMessage = "Simple message\n\nDetails"; - - String formattedMessage = CommitMessageComponent - .formatIssuesInCommitMessage(commitMessage); - assertEquals(null, formattedMessage); - } - - @Test - public void commitFormat_MultipleLines_notOk() { - String commitMessage = "Simple message\nDetails"; - - String formattedMessage = CommitMessageComponent - .formatIssuesInCommitMessage(commitMessage); - assertEquals(UIText.CommitMessageComponent_MessageSecondLineNotEmpty, - formattedMessage); - } - - @Test - public void commitFormat_MultipleLines_notOk2() { - String commitMessage = "Simple message\n \nDetails"; - - String formattedMessage = CommitMessageComponent - .formatIssuesInCommitMessage(commitMessage); - assertEquals(UIText.CommitMessageComponent_MessageSecondLineNotEmpty, - formattedMessage); - } - - @Test - public void commitMessageProvider_noProvider() throws Exception { - CommitMessageComponent commitMessageComponent = newCommitMessageComponent( - createProviderList()); - - String calculatedCommitMessage = commitMessageComponent - .calculateCommitMessage(Collections.emptyList()); - - assertEquals("", calculatedCommitMessage); - } - - @Test - public void commitMessageProvider_oneProvider() throws Exception { - String message = "example single-line commit message"; - - CommitMessageComponent commitMessageComponent = newCommitMessageComponent( - createProviderList(message)); - - String calculatedCommitMessage = commitMessageComponent - .calculateCommitMessage(Collections.emptyList()); - - assertEquals(message, calculatedCommitMessage); - } - - @Test - public void commitMessageProvider_twoProviders() throws Exception { - String message1 = "example single-line commit message"; - String message2 = "example multi-line\n\ncommit message"; - - CommitMessageComponent commitMessageComponent = newCommitMessageComponent( - createProviderList(message1, message2)); - - String calculatedCommitMessage = commitMessageComponent - .calculateCommitMessage(Collections.emptyList()); - - assertEquals(message1 + "\n\n" + message2, calculatedCommitMessage); - } - - @Test - public void commitMessageProvider_oneCrashingProvider() throws Exception { - - CommitMessageComponent commitMessageComponent = newCommitMessageComponent( - Arrays.asList(new CrashingCommitMessageProvider())); - - String calculatedCommitMessage = commitMessageComponent - .calculateCommitMessage(Collections.emptyList()); - - assertEquals("", calculatedCommitMessage); - } - - @Test - public void commitMessageProvider_twoProvidersSecondOneCrashing() - throws Exception { - String message = "example single-line commit message"; - List providers = createProviderList(message); - providers.add(new CrashingCommitMessageProvider()); - - CommitMessageComponent commitMessageComponent = newCommitMessageComponent( - providers); - - String calculatedCommitMessage = commitMessageComponent - .calculateCommitMessage(Collections.emptyList()); - - assertEquals(message, calculatedCommitMessage); - } - - @Test - public void commitMessageProvider_twoProvidersFirstOneCrashing() - throws Exception { - String message = "example single-line commit message"; - List providers = createProviderList(message); - providers.add(0, new CrashingCommitMessageProvider()); - - CommitMessageComponent commitMessageComponent = newCommitMessageComponent( - providers); - - String calculatedCommitMessage = commitMessageComponent - .calculateCommitMessage(Collections.emptyList()); - - assertEquals(message, calculatedCommitMessage); - } - - @Test - public void commitMessageProvider_multipleProvidersWithCrashAndNull() - throws Exception { - String singleLineMessage = "example single-line commit message"; - String multiLineMessage = "example\nmulti-line\n\ncommit message"; - List providers = createProviderList( - multiLineMessage + "\n\n\n", null, "\n" + singleLineMessage); - providers.add(0, new CrashingCommitMessageProvider()); - providers.add(3, new CrashingCommitMessageProvider()); - - CommitMessageComponent commitMessageComponent = newCommitMessageComponent( - providers); - - String calculatedCommitMessage = commitMessageComponent - .calculateCommitMessage(Collections.emptyList()); - - assertEquals(multiLineMessage + "\n\n" + singleLineMessage, - calculatedCommitMessage); - } - - private CommitMessageComponent newCommitMessageComponent( - List providers) { - // Create anonymous subclass, as mocking does not currently work. - // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=349164 - return new CommitMessageComponent(null) { - - @Override - List getCommitMessageProviders() { - return providers; - } - }; - } - - private List createProviderList( - String... messages) { - List providerList = new ArrayList<>(); - - for (String message : messages) { - providerList.add(new ICommitMessageProvider() { - - @Override - public String getMessage(IResource[] resources) { - return message; - } - }); - } - - return providerList; - } - - private static class CrashingCommitMessageProvider - implements ICommitMessageProvider { - - @Override - public String getMessage(IResource[] resources) { - throw new IllegalStateException( - "CrashingCommitMessageProvider fails on purpose."); - } - - } - -} +/******************************************************************************* + * Copyright (C) 2015 SAP SE (Christian Georgi ) + * + * 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 + *******************************************************************************/ +package org.eclipse.egit.ui.internal.dialogs; + +import static org.junit.Assert.assertEquals; + +import org.eclipse.egit.ui.internal.UIText; +import org.junit.Test; + +public class CommitMessageComponentTest { + + @Test + public void commitFormat_simple() { + String commitMessage = "Simple message"; + + String formattedMessage = CommitMessageComponent + .formatIssuesInCommitMessage(commitMessage); + assertEquals(null, formattedMessage); + } + + @Test + public void commitFormat_trailingWhitespace_ok() { + String commitMessage = "Simple message\n\n\n"; + + String formattedMessage = CommitMessageComponent + .formatIssuesInCommitMessage(commitMessage); + assertEquals(null, formattedMessage); + } + + @Test + public void commitFormat_MultipleLines_ok() { + String commitMessage = "Simple message\n\nDetails"; + + String formattedMessage = CommitMessageComponent + .formatIssuesInCommitMessage(commitMessage); + assertEquals(null, formattedMessage); + } + + @Test + public void commitFormat_MultipleLines_notOk() { + String commitMessage = "Simple message\nDetails"; + + String formattedMessage = CommitMessageComponent + .formatIssuesInCommitMessage(commitMessage); + assertEquals(UIText.CommitMessageComponent_MessageSecondLineNotEmpty, + formattedMessage); + } + + @Test + public void commitFormat_MultipleLines_notOk2() { + String commitMessage = "Simple message\n \nDetails"; + + String formattedMessage = CommitMessageComponent + .formatIssuesInCommitMessage(commitMessage); + assertEquals(UIText.CommitMessageComponent_MessageSecondLineNotEmpty, + formattedMessage); + } + +} diff --git a/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/test/stagview/AbstractStagingViewTestCase.java b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/test/stagview/AbstractStagingViewTestCase.java new file mode 100644 index 000000000..519951d75 --- /dev/null +++ b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/test/stagview/AbstractStagingViewTestCase.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (C) 2017 Thomas Wolf + * + * 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 + *******************************************************************************/ +package org.eclipse.egit.ui.test.stagview; + +import java.io.File; + +import org.eclipse.egit.core.JobFamilies; +import org.eclipse.egit.ui.Activator; +import org.eclipse.egit.ui.common.LocalRepositoryTestCase; +import org.eclipse.egit.ui.internal.repository.RepositoriesView; +import org.eclipse.egit.ui.internal.staging.StagingView; +import org.eclipse.egit.ui.test.TestUtil; +import org.eclipse.egit.ui.view.repositories.GitRepositoriesViewTestUtils; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotView; +import org.eclipse.swtbot.swt.finder.widgets.SWTBotTree; +import org.eclipse.swtbot.swt.finder.widgets.SWTBotTreeItem; +import org.junit.After; +import org.junit.Before; + +public abstract class AbstractStagingViewTestCase + extends LocalRepositoryTestCase { + + protected static final GitRepositoriesViewTestUtils repoViewUtil = new GitRepositoriesViewTestUtils(); + + protected File repositoryFile; + + protected Repository repository; + + @Before + public void before() throws Exception { + repositoryFile = createProjectAndCommitToRepository(); + repository = lookupRepository(repositoryFile); + TestUtil.configureTestCommitterAsUser(repository); + Activator.getDefault().getRepositoryUtil() + .addConfiguredRepository(repositoryFile); + + selectRepositoryNode(); + } + + @After + public void after() { + TestUtil.hideView(RepositoriesView.VIEW_ID); + TestUtil.hideView(StagingView.VIEW_ID); + Activator.getDefault().getRepositoryUtil().removeDir(repositoryFile); + } + + protected void setContent(String content) throws Exception { + setTestFileContent(content); + TestUtil.joinJobs(JobFamilies.INDEX_DIFF_CACHE_UPDATE); + } + + protected void selectRepositoryNode() throws Exception { + SWTBotView repositoriesView = TestUtil + .showView(RepositoriesView.VIEW_ID); + SWTBotTree tree = repositoriesView.bot().tree(); + + SWTBotTreeItem repoNode = repoViewUtil.getRootItem(tree, + repositoryFile); + repoNode.select(); + } + +} diff --git a/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/test/stagview/CommitMessageProvidersTest.java b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/test/stagview/CommitMessageProvidersTest.java new file mode 100644 index 000000000..6f7760b6a --- /dev/null +++ b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/test/stagview/CommitMessageProvidersTest.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright (C) 2017, Stefan Rademacher and others. + * + * 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 + *******************************************************************************/ +package org.eclipse.egit.ui.test.stagview; + +import static org.junit.Assert.assertEquals; + +import org.eclipse.core.resources.IResource; +import org.eclipse.egit.ui.CommitMessageWithCaretPosition; +import org.eclipse.egit.ui.ICommitMessageProvider; +import org.eclipse.egit.ui.ICommitMessageProvider2; +import org.eclipse.egit.ui.common.StagingViewTester; +import org.junit.After; +import org.junit.Test; + +public class CommitMessageProvidersTest extends AbstractStagingViewTestCase { + + @After + public void resetCommitMessageProvider() { + TestCommitMessageProviderExtensionFactory.INSTANCE.reset(); + } + + private void assertPosition(int actualPosition, String message) { + // Convention: expected position in message is marked by "><" + int expectedPosition = message.indexOf("><") + 1; + assertEquals("Position mismatch", expectedPosition, actualPosition); + } + + @Test + public void testCaretPosition() throws Exception { + setContent("I have changed this"); + TestCommitMessageProviderExtensionFactory.INSTANCE + .setCommitMessageProviders(new TestCommitMessageProvider( + "Caret test\n\nCaret is supposed to be there: ", + "\n\nThis is a commit message from testCaretPosition")); + StagingViewTester stagingViewTester = StagingViewTester + .openStagingView(); + + stagingViewTester.stageFile(FILE1_PATH); + + assertPosition(stagingViewTester.getCaretPosition(), + stagingViewTester.getCommitMessage()); + } + + @Test + public void testCaretPositionUndefined() throws Exception { + setContent("I have changed this"); + TestCommitMessageProviderExtensionFactory.INSTANCE + .setCommitMessageProviders(new ICommitMessageProvider() { + + @Override + public String getMessage(IResource[] resources) { + return "Commit msg from testCaretPositionUndefined"; + } + + }); + StagingViewTester stagingViewTester = StagingViewTester + .openStagingView(); + + stagingViewTester.stageFile(FILE1_PATH); + + assertPosition(stagingViewTester.getCaretPosition(), + stagingViewTester.getCommitMessage()); + } + + @Test + public void testTwoProvidersFromSameExtension() throws Exception { + setContent("I have changed this"); + TestCommitMessageProvider provider1 = new TestCommitMessageProvider( + "Caret test\n\nCaret is supposed to be there: ", + "\n\nThis is a commit message from testTwoProvidersFromSameExtension"); + + TestCommitMessageProvider provider2 = new TestCommitMessageProvider( + "Another commit message. \n\nCaret is NOT supposed to be there: ", + "\n\nThis is a commit message from testTwoProvidersFromSameExtension"); + TestCommitMessageProviderExtensionFactory.INSTANCE + .setCommitMessageProviders(provider1, provider2); + + StagingViewTester stagingViewTester = StagingViewTester + .openStagingView(); + + stagingViewTester.stageFile(FILE1_PATH); + + assertPosition(stagingViewTester.getCaretPosition(), + stagingViewTester.getCommitMessage()); + + assertEquals( + provider1.getMessage(null) + "\n\n" + + provider2.getMessage(null), + stagingViewTester.getCommitMessage()); + } + + private static class TestCommitMessageProvider + implements ICommitMessageProvider2 { + + private String message; + + private int pos; + + public TestCommitMessageProvider(String prefix, String suffix) { + message = prefix + "><" + suffix; + pos = prefix.length() + 1; + } + + @Override + public String getMessage(IResource[] resources) { + return message; + } + + @Override + public CommitMessageWithCaretPosition getCommitMessageWithPosition( + IResource[] resources) { + return new CommitMessageWithCaretPosition(message, pos); + } + + } + +} diff --git a/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/test/stagview/StagingViewTest.java b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/test/stagview/StagingViewTest.java index 7047e0b8e..5e3905a3f 100644 --- a/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/test/stagview/StagingViewTest.java +++ b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/test/stagview/StagingViewTest.java @@ -14,54 +14,16 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import java.io.File; - -import org.eclipse.egit.core.JobFamilies; -import org.eclipse.egit.ui.Activator; -import org.eclipse.egit.ui.common.LocalRepositoryTestCase; import org.eclipse.egit.ui.common.StagingViewTester; -import org.eclipse.egit.ui.internal.repository.RepositoriesView; -import org.eclipse.egit.ui.internal.staging.StagingView; import org.eclipse.egit.ui.test.CommitMessageUtil; import org.eclipse.egit.ui.test.TestUtil; -import org.eclipse.egit.ui.view.repositories.GitRepositoriesViewTestUtils; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryState; import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotView; -import org.eclipse.swtbot.swt.finder.widgets.SWTBotTree; -import org.eclipse.swtbot.swt.finder.widgets.SWTBotTreeItem; -import org.junit.After; -import org.junit.Before; import org.junit.Test; -public class StagingViewTest extends LocalRepositoryTestCase { - - private static final GitRepositoriesViewTestUtils repoViewUtil = new GitRepositoriesViewTestUtils(); - - private File repositoryFile; - - private Repository repository; - - @Before - public void before() throws Exception { - repositoryFile = createProjectAndCommitToRepository(); - repository = lookupRepository(repositoryFile); - TestUtil.configureTestCommitterAsUser(repository); - Activator.getDefault().getRepositoryUtil() - .addConfiguredRepository(repositoryFile); - - selectRepositoryNode(); - } - - @After - public void after() { - TestUtil.hideView(RepositoriesView.VIEW_ID); - TestUtil.hideView(StagingView.VIEW_ID); - Activator.getDefault().getRepositoryUtil().removeDir(repositoryFile); - } +public class StagingViewTest extends AbstractStagingViewTestCase { @Test public void testCommitSingleFile() throws Exception { @@ -154,19 +116,4 @@ public class StagingViewTest extends LocalRepositoryTestCase { assertTrue(commitMessage.indexOf("Signed-off-by") > 0); stagingViewTester.commit(); } - - private void setContent(String content) throws Exception { - setTestFileContent(content); - TestUtil.joinJobs(JobFamilies.INDEX_DIFF_CACHE_UPDATE); - } - - private void selectRepositoryNode() throws Exception { - SWTBotView repositoriesView = TestUtil - .showView(RepositoriesView.VIEW_ID); - SWTBotTree tree = repositoriesView.bot().tree(); - - SWTBotTreeItem repoNode = repoViewUtil - .getRootItem(tree, repositoryFile); - repoNode.select(); - } } diff --git a/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/test/stagview/TestCommitMessageProviderExtensionFactory.java b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/test/stagview/TestCommitMessageProviderExtensionFactory.java new file mode 100644 index 000000000..ada383f12 --- /dev/null +++ b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/test/stagview/TestCommitMessageProviderExtensionFactory.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (C) 2017 Thomas Wolf and others + * + * 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 + *******************************************************************************/ +package org.eclipse.egit.ui.test.stagview; + +import java.util.LinkedList; +import java.util.Queue; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IExecutableExtensionFactory; +import org.eclipse.egit.ui.ICommitMessageProvider; + +public class TestCommitMessageProviderExtensionFactory + implements IExecutableExtensionFactory { + + // Indirection needed since the extension point may create new factory + // instances. + protected static final TestCommitMessageProviderFactory INSTANCE = new TestCommitMessageProviderFactory(); + + @Override + public Object create() throws CoreException { + return INSTANCE.create(); + } + + static class TestCommitMessageProviderFactory + implements IExecutableExtensionFactory { + + private Queue providers = new LinkedList<>(); + + private final ICommitMessageProvider emptyProvider = new ICommitMessageProvider() { + @Override + public String getMessage(IResource[] resources) { + return ""; + } + }; + + @Override + public Object create() throws CoreException { + if (!providers.isEmpty()) { + ICommitMessageProvider p = providers.poll(); + if (p != null) { + return p; + } + } + return emptyProvider; + } + + public void reset() { // To be called in @After + providers.clear(); + } + + public void setCommitMessageProviders( + ICommitMessageProvider... newProviders) { + providers.clear(); + for (ICommitMessageProvider p : newProviders) { + providers.add(p); + } + } + + } + +} diff --git a/org.eclipse.egit.ui/schema/commitMessageProvider.exsd b/org.eclipse.egit.ui/schema/commitMessageProvider.exsd index a62e83831..648b5c182 100644 --- a/org.eclipse.egit.ui/schema/commitMessageProvider.exsd +++ b/org.eclipse.egit.ui/schema/commitMessageProvider.exsd @@ -17,7 +17,7 @@ - + @@ -82,8 +82,8 @@ - There is an interface org.eclipse.egit.ui.ICommitMessageProvider. -This is the only interface you must implement to use the extension point. + There is an interface org.eclipse.egit.ui.ICommitMessageProvider, which you must implement to use the extension point. +There is an interface org.eclipse.egit.ui.ICommitMessageProvider2, which extends ICommitMessageProvider. Implement this interface, if you want to provide a commit message and a caret position. diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/Activator.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/Activator.java index f45d76d28..13c62155a 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/Activator.java +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/Activator.java @@ -208,6 +208,18 @@ public class Activator extends AbstractUIPlugin implements DebugOptionsListener } /** + * Utility method to log warnings for this plug-in. + * + * @param message + * User comprehensible message + * @param thr + * The exception through which we noticed the warning + */ + public static void logWarning(final String message, final Throwable thr) { + handleIssue(IStatus.WARNING, message, thr, false); + } + + /** * @param message * @param e */ diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/CommitMessageWithCaretPosition.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/CommitMessageWithCaretPosition.java new file mode 100644 index 000000000..a34be7cb1 --- /dev/null +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/CommitMessageWithCaretPosition.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (C) 2017, Stefan Rademacher + * + * 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 + *******************************************************************************/ +package org.eclipse.egit.ui; + +import java.util.Objects; + +/** + * This class represents a commit message with a caret position. + */ +public class CommitMessageWithCaretPosition { + + /** + * This constant defines the value for an undefined caret position. + */ + public static final int NO_POSITION = -1; + + private final String message; + + private final int caretPosition; + + /** + * Constructor for creating an immutable value object, that represents a + * commit message and a caret position within that message. + * + * @param message + * the commit message + * @param caretPosition + * the caret position within the commit message + */ + public CommitMessageWithCaretPosition(String message, + int caretPosition) { + this.message = message; + this.caretPosition = caretPosition; + } + + /** + * @return the commit message + */ + public String getMessage() { + return message; + } + + /** + * @return the desired caret position within the commit message + */ + public int getDesiredCaretPosition() { + return caretPosition; + } + + @Override + public int hashCode() { + return Objects.hash(message, Integer.valueOf(caretPosition)); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + CommitMessageWithCaretPosition other = (CommitMessageWithCaretPosition) obj; + return caretPosition == other.caretPosition + && Objects.equals(message, other.message); + } + +} \ No newline at end of file diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/ICommitMessageProvider2.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/ICommitMessageProvider2.java new file mode 100644 index 000000000..f2a392951 --- /dev/null +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/ICommitMessageProvider2.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (C) 2017, Stefan Rademacher + * + * + * 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 + *******************************************************************************/ +package org.eclipse.egit.ui; + +import org.eclipse.core.resources.IResource; +import org.eclipse.egit.ui.internal.dialogs.CommitDialog; + +/** + * This interface must be implemented to be a commit message provider, that does + * not only provide the message itself, but also a caret position within this + * message. This message will be added to the text field in the + * {@link CommitDialog}.
+ * + * @see ICommitMessageProvider + * @see CommitDialog + */ +public interface ICommitMessageProvider2 extends ICommitMessageProvider { + + /** + * Unlike {@link #getMessage(IResource[])}, this method provides a way to + * retrieve not only a commit message but also a caret position within the + * message. + * + * @param resources + * the selected resources, when this method is called. + * + * @return an object, containing the commit message and the caret position + * within the message + */ + CommitMessageWithCaretPosition getCommitMessageWithPosition( + IResource[] resources); + +} diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/UIText.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/UIText.java index 7ecf20d83..a2cf8274a 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/UIText.java +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/UIText.java @@ -2360,6 +2360,12 @@ public class UIText extends NLS { public static String CommitDialog_ErrorCreatingCommitMessageProvider; /** */ + public static String CommitDialog_CaretPositionOutOfBounds; + + /** */ + public static String CommitDialog_IgnoreCaretPosition; + + /** */ public static String CommitDialog_ConfigureLink; /** */ diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageBuilder.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageBuilder.java new file mode 100644 index 000000000..d841f8ee6 --- /dev/null +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageBuilder.java @@ -0,0 +1,225 @@ +/******************************************************************************* + * Copyright (C) 2017, Stefan Rademacher + * + * + * 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 + *******************************************************************************/ +package org.eclipse.egit.ui.internal.dialogs; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.Platform; +import org.eclipse.egit.core.internal.util.ProjectUtil; +import org.eclipse.egit.core.internal.util.ResourceUtil; +import org.eclipse.egit.ui.Activator; +import org.eclipse.egit.ui.CommitMessageWithCaretPosition; +import org.eclipse.egit.ui.ICommitMessageProvider; +import org.eclipse.egit.ui.ICommitMessageProvider2; +import org.eclipse.egit.ui.internal.UIText; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.lib.Repository; + +class CommitMessageBuilder { + + /** + * Constant for the extension point for the commit message provider. + */ + private static final String COMMIT_MESSAGE_PROVIDER_ID = "org.eclipse.egit.ui.commitMessageProvider"; //$NON-NLS-1$ + + private static final String MESSAGE_SEPARATOR = "\n\n"; //$NON-NLS-1$ + + private final IResource[] resourcesArray; + + @NonNull + private final Repository repository; + + private boolean isMessageEmpty; + + /** + * Creates a CommitMessageBuilder for the specified repository and the + * files, that are about to be committed. + * + * @param repository + * the repository, messages are built for + * @param paths + * list of file paths, selected for the next commit + */ + CommitMessageBuilder(@NonNull Repository repository, + Collection paths) { + this.repository = repository; + this.resourcesArray = toResourceArray(paths); + } + + /** + * Returns an object, containing the commit message and the caret position + * within that message. + * + * @return a commit message with caret position. The caret position is 0, if + * there was no {@link ICommitMessageProvider2} providing a caret + * position. + */ + CommitMessageWithCaretPosition build() { + StringBuilder finalMessage = new StringBuilder(); + int caretPosition = CommitMessageWithCaretPosition.NO_POSITION; + isMessageEmpty = true; + + for (ICommitMessageProvider provider : getCommitMessageProviders()) { + String message = ""; //$NON-NLS-1$ + try { + if (provider instanceof ICommitMessageProvider2) { + CommitMessageWithCaretPosition commitMessageWithPosition = ((ICommitMessageProvider2) provider) + .getCommitMessageWithPosition(resourcesArray); + if (commitMessageWithPosition != null) { + caretPosition = updateCaretPosition(finalMessage, + caretPosition, commitMessageWithPosition, + (ICommitMessageProvider2) provider); + } + message = getCommitMessage(commitMessageWithPosition); + } else { + message = append( + provider.getMessage(resourcesArray)); + } + } catch (RuntimeException e) { + Activator.logError(e.getMessage(), e); + } + finalMessage.append(message); + isMessageEmpty = finalMessage.length() == 0; + } + return new CommitMessageWithCaretPosition(finalMessage.toString(), + Math.max(0, caretPosition)); + } + + private String getCommitMessage( + CommitMessageWithCaretPosition messageWithPosition) { + if (messageWithPosition == null) { + return ""; //$NON-NLS-1$ + } else { + return append(messageWithPosition.getMessage()); + } + } + + private String append(String msg) { + StringBuilder returnMsg = new StringBuilder(); + if (msg != null && !msg.trim().isEmpty()) { + if (!isMessageEmpty) { + returnMsg.append(MESSAGE_SEPARATOR); + } + returnMsg.append(msg); + } + return returnMsg.toString(); + } + + @SuppressWarnings("boxing") + private int updateCaretPosition(StringBuilder currentMessage, + int currentCaretPosition, + CommitMessageWithCaretPosition messageWithPosition, + ICommitMessageProvider2 provider) { + int pos = currentCaretPosition; + if (currentCaretPosition == CommitMessageWithCaretPosition.NO_POSITION) { + String providedMessage = messageWithPosition.getMessage(); + if (providedMessage == null || providedMessage.trim().isEmpty()) { + return pos; + } + int providedCaretPosition = messageWithPosition + .getDesiredCaretPosition(); + if (providedCaretPosition == CommitMessageWithCaretPosition.NO_POSITION) { + return pos; + } + if (providedCaretPosition > providedMessage.length() + || providedCaretPosition < 0) { + Activator.logWarning( + MessageFormat.format( + UIText.CommitDialog_CaretPositionOutOfBounds, + provider.getClass().getName(), + providedCaretPosition), + null); + return CommitMessageWithCaretPosition.NO_POSITION; + + } else { + pos = currentMessage.length(); + if (currentMessage.length() > 0) { + pos += MESSAGE_SEPARATOR.length(); + } + pos += providedCaretPosition; + } + } else { + Activator + .logWarning( + MessageFormat.format( + UIText.CommitDialog_IgnoreCaretPosition, + provider.getClass().getName()), + null); + } + + return pos; + } + + List getCommitMessageProviders() { + List providers = new ArrayList<>(); + + IConfigurationElement[] configs = Platform.getExtensionRegistry() + .getConfigurationElementsFor(COMMIT_MESSAGE_PROVIDER_ID); + for (IConfigurationElement config : configs) { + Object provider; + String contributorName = ""; //$NON-NLS-1$ + String extensionId = ""; //$NON-NLS-1$ + try { + extensionId = config.getDeclaringExtension() + .getUniqueIdentifier(); + contributorName = config.getContributor().getName(); + provider = config.createExecutableExtension("class");//$NON-NLS-1$ + if (provider instanceof ICommitMessageProvider) { + providers.add((ICommitMessageProvider) provider); + } else { + Activator.logError( + MessageFormat.format( + UIText.CommitDialog_WrongTypeOfCommitMessageProvider, + extensionId, contributorName), + null); + } + } catch (CoreException | RuntimeException e) { + Activator + .logError( + MessageFormat.format( + UIText.CommitDialog_ErrorCreatingCommitMessageProvider, + extensionId, contributorName), + e); + } + } + return providers; + } + + private IResource[] toResourceArray(Collection paths) { + Set resources = new HashSet<>(); + for (String path : paths) { + IFile file = null; + if (path != null) { + file = ResourceUtil.getFileForLocation(repository, path, + false); + } + if (file != null) { + resources.add(file.getProject()); + } + } + if (resources.size() == 0) { + resources + .addAll(Arrays + .asList(ProjectUtil.getProjects(repository))); + } + return resources.toArray(new IResource[0]); + } + +} diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageComponent.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageComponent.java index 5c2327013..8ad6b9415 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageComponent.java +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageComponent.java @@ -18,29 +18,14 @@ *******************************************************************************/ package org.eclipse.egit.ui.internal.dialogs; -import java.io.File; import java.io.IOException; -import java.net.URI; -import java.text.MessageFormat; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.ResourcesPlugin; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IConfigurationElement; -import org.eclipse.core.runtime.IExtensionRegistry; -import org.eclipse.core.runtime.Platform; + import org.eclipse.egit.core.RevUtils; import org.eclipse.egit.core.internal.gerrit.GerritUtil; -import org.eclipse.egit.core.internal.util.ProjectUtil; import org.eclipse.egit.ui.Activator; -import org.eclipse.egit.ui.ICommitMessageProvider; +import org.eclipse.egit.ui.CommitMessageWithCaretPosition; import org.eclipse.egit.ui.UIPreferences; import org.eclipse.egit.ui.UIUtils; import org.eclipse.egit.ui.UIUtils.IPreviousValueProposalHandler; @@ -119,11 +104,6 @@ public class CommitMessageComponent { private static final String EMPTY_STRING = ""; //$NON-NLS-1$ - /** - * Constant for the extension point for the commit message provider - */ - private static final String COMMIT_MESSAGE_PROVIDER_ID = "org.eclipse.egit.ui.commitMessageProvider"; //$NON-NLS-1$ - private static final String COMMITTER_VALUES_PREF = "CommitDialog.committerValues"; //$NON-NLS-1$ private static final String AUTHOR_VALUES_PREF = "CommitDialog.authorValues"; //$NON-NLS-1$ @@ -140,10 +120,16 @@ public class CommitMessageComponent { private String commitMessage = null; + private int caretPosition = CommitMessageComponentState.CARET_DEFAULT_POSITION; + private String commitMessageBeforeAmending = EMPTY_STRING; + private int carePositionBeforeAmending = CommitMessageComponentState.CARET_DEFAULT_POSITION; + private String previousCommitMessage = EMPTY_STRING; + private int previousCaretPosition = CommitMessageComponentState.CARET_DEFAULT_POSITION; + private String author = null; private String previousAuthor = null; @@ -199,8 +185,11 @@ public class CommitMessageComponent { public void resetState() { originalChangeId = null; commitMessage = null; + caretPosition = CommitMessageComponentState.CARET_DEFAULT_POSITION; commitMessageBeforeAmending = EMPTY_STRING; + carePositionBeforeAmending = CommitMessageComponentState.CARET_DEFAULT_POSITION; previousCommitMessage = EMPTY_STRING; + previousCaretPosition = CommitMessageComponentState.CARET_DEFAULT_POSITION; author = null; previousAuthor = null; committer = null; @@ -235,6 +224,15 @@ public class CommitMessageComponent { } /** + * Preset a caret position within the commit message. + * + * @param p + */ + public void setCaretPosition(int p) { + this.caretPosition = p; + } + + /** * @return The author to set for the commit */ public String getAuthor() { @@ -339,6 +337,7 @@ public class CommitMessageComponent { public void setAmendAllowed(boolean amendAllowed) { this.amendAllowed = amendAllowed; commitMessageBeforeAmending = EMPTY_STRING; + carePositionBeforeAmending = CommitMessageComponentState.CARET_DEFAULT_POSITION; } /** @@ -350,12 +349,18 @@ public class CommitMessageComponent { originalChangeId = null; authorText.setText(author); commitText.setText(commitMessageBeforeAmending); + commitText.getTextWidget() + .setCaretOffset(carePositionBeforeAmending); commitMessageBeforeAmending = EMPTY_STRING; + carePositionBeforeAmending = CommitMessageComponentState.CARET_DEFAULT_POSITION; } else { getHeadCommitInfo(); saveOriginalChangeId(); commitMessageBeforeAmending = commitText.getText(); + carePositionBeforeAmending = commitText.getTextWidget() + .getCaretOffset(); commitText.setText(previousCommitMessage); + commitText.getTextWidget().setCaretOffset(previousCaretPosition); if (previousAuthor != null) authorText.setText(previousAuthor); } @@ -374,6 +379,7 @@ public class CommitMessageComponent { */ public void updateStateFromUI() { commitMessage = commitText.getText(); + caretPosition = commitText.getTextWidget().getCaretOffset(); author = authorText.getText().trim(); committer = committerText.getText().trim(); } @@ -383,6 +389,7 @@ public class CommitMessageComponent { */ public void updateUIFromState() { commitText.setText(commitMessage); + commitText.getTextWidget().setCaretOffset(caretPosition); authorText.setText(author); committerText.setText(committer); } @@ -599,7 +606,13 @@ public class CommitMessageComponent { if (amending) getHeadCommitInfo(); - String calculatedCommitMessage = calculateCommitMessage(filesToCommit); + CommitMessageWithCaretPosition commitMessageWithCaretPosition = new CommitMessageBuilder( + repository, filesToCommit).build(); + + String calculatedCommitMessage = calculateCommitMessage( + commitMessageWithCaretPosition); + int calculatedCaretPosition = calculateCaretPosition( + commitMessageWithCaretPosition); boolean calculatedMessageHasChangeId = findOffsetOfChangeIdLine(calculatedCommitMessage) > 0; commitText.setText(calculatedCommitMessage); authorText.setText(getSafeString(author)); @@ -617,6 +630,9 @@ public class CommitMessageComponent { } updateSignedOffButton(); updateChangeIdButton(); + + commitText.getTextWidget() + .setCaretOffset(calculatedCaretPosition); } /** @@ -663,83 +679,33 @@ public class CommitMessageComponent { } /** - * @param paths + * @param messageWithCaretPosition * @return the calculated commit message */ - String calculateCommitMessage(Collection paths) { + String calculateCommitMessage( + CommitMessageWithCaretPosition messageWithCaretPosition) { if (commitMessage != null) { - // special case for merge + // special case for merge / cherry-pick return commitMessage; } if (amending) return previousCommitMessage; - StringBuilder calculatedCommitMessage = new StringBuilder(); - - Set resources = new HashSet<>(); - for (String path : paths) { - IFile file = findFile(path); - if (file != null) - resources.add(file.getProject()); - } - if (resources.size() == 0 && repository != null) { - resources - .addAll(Arrays.asList(ProjectUtil.getProjects(repository))); - } - List messageProviders = getCommitMessageProviders(); - IResource[] resourcesArray = resources.toArray(new IResource[0]); - String providedMessageSeparator = "\n\n"; //$NON-NLS-1$ - for (ICommitMessageProvider messageProvider : messageProviders) { - String message = null; - try { - message = messageProvider.getMessage(resourcesArray); - } catch (RuntimeException e) { - Activator.logError(e.getMessage(), e); - } + return messageWithCaretPosition.getMessage(); + } - if (message != null && !message.trim().isEmpty()) { - if (calculatedCommitMessage.length() > 0) { - calculatedCommitMessage.append(providedMessageSeparator); - } - calculatedCommitMessage.append((message.trim())); - } + private int calculateCaretPosition( + CommitMessageWithCaretPosition messageWithCaretPosition) { + if (commitMessage != null) { + // special case for merge / cherry-pick + return caretPosition; } - return calculatedCommitMessage.toString(); - } - - List getCommitMessageProviders() { - List providers = new ArrayList<>(); + if (amending) + return previousCaretPosition; - IExtensionRegistry registry = Platform.getExtensionRegistry(); - IConfigurationElement[] configs = registry - .getConfigurationElementsFor(COMMIT_MESSAGE_PROVIDER_ID); - for (IConfigurationElement config : configs) { - Object provider; - String contributorName = ""; //$NON-NLS-1$ - String extensionId = ""; //$NON-NLS-1$ - try { - extensionId = config.getDeclaringExtension() - .getUniqueIdentifier(); - contributorName = config.getContributor().getName(); - provider = config.createExecutableExtension("class");//$NON-NLS-1$ - if (provider instanceof ICommitMessageProvider) { - providers.add((ICommitMessageProvider) provider); - } else { - Activator.logError(MessageFormat.format( - UIText.CommitDialog_WrongTypeOfCommitMessageProvider, - extensionId, contributorName), null); - } - } catch (CoreException | RuntimeException e) { - Activator.logError( - MessageFormat.format( - UIText.CommitDialog_ErrorCreatingCommitMessageProvider, - extensionId, contributorName), - e); - } - } - return providers; + return messageWithCaretPosition.getDesiredCaretPosition(); } private void saveOriginalChangeId() { @@ -887,17 +853,6 @@ public class CommitMessageComponent { input.length()); } - // TODO: move to utils - private IFile findFile(String path) { - URI uri = new File(repository.getWorkTree(), path).toURI(); - IFile[] workspaceFiles = ResourcesPlugin.getWorkspace().getRoot() - .findFilesForLocationURI(uri); - if (workspaceFiles.length > 0) - return workspaceFiles[0]; - else - return null; - } - /** * @param signedOffButtonSelection */ diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageComponentState.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageComponentState.java index 6f9a17089..5aea9b684 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageComponentState.java +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageComponentState.java @@ -16,7 +16,12 @@ import org.eclipse.jgit.lib.ObjectId; */ public class CommitMessageComponentState { + static final int CARET_DEFAULT_POSITION = 0; + private String commitMessage; + + private int caretPosition; + private String committer; private String author; private boolean amend; @@ -37,6 +42,20 @@ public class CommitMessageComponentState { } /** + * @return caretPosition + */ + public int getCaretPosition() { + return caretPosition; + } + + /** + * @param caretPosition + */ + public void setCaretPosition(int caretPosition) { + this.caretPosition = caretPosition; + } + + /** * @return committer */ public String getCommitter() { diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageComponentStateManager.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageComponentStateManager.java index ea553aa59..3c2b253c5 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageComponentStateManager.java +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageComponentStateManager.java @@ -24,9 +24,13 @@ public class CommitMessageComponentStateManager { private static final String EMPTY = "empty"; //$NON-NLS-1$ - private static final int MEMBER_COUNT = 5; // number of members in + private static final int MEMBER_COUNT = 6; // number of members in // CommitMessageComponentState + // number of members in CommitMessageComponentState, before caret + // positioning was introduced + private static final int MEMBER_COUNT_WITHOUT_CARET_POSITION = 5; + /** * @param repository * @param state @@ -36,7 +40,9 @@ public class CommitMessageComponentStateManager { IDialogSettings dialogSettings = getDialogSettings(); String[] values = new String[] { Boolean.toString(state.getAmend()), state.getAuthor(), state.getCommitMessage(), - state.getCommitter(), state.getHeadCommit().getName().toString() }; + state.getCommitter(), + state.getHeadCommit().getName().toString(), + String.valueOf(state.getCaretPosition()) }; dialogSettings.put(repository.getDirectory().getAbsolutePath(), values); } @@ -48,14 +54,23 @@ public class CommitMessageComponentStateManager { IDialogSettings dialogSettings = getDialogSettings(); String[] values = dialogSettings.getArray(repository.getDirectory() .getAbsolutePath()); - if (values == null || values.length < MEMBER_COUNT) + if (values == null + || values.length < MEMBER_COUNT_WITHOUT_CARET_POSITION) { return null; + } + CommitMessageComponentState state = new CommitMessageComponentState(); state.setAmend(Boolean.parseBoolean(values[0])); state.setAuthor(values[1]); state.setCommitMessage(values[2]); state.setCommitter(values[3]); state.setHeadCommit(ObjectId.fromString(values[4])); + if (values.length >= MEMBER_COUNT) { + state.setCaretPosition(Integer.parseInt(values[5])); + } else { + state.setCaretPosition( + CommitMessageComponentState.CARET_DEFAULT_POSITION); + } return state; } diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/staging/StagingView.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/staging/StagingView.java index 3d038d72f..c4abbe9ca 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/staging/StagingView.java +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/staging/StagingView.java @@ -3825,11 +3825,14 @@ public class StagingView extends ViewPart getCommitId(helper.getPreviousCommit())); commitMessageComponent.enableListeners(false); commitMessageComponent.setAuthor(oldState.getAuthor()); - if (headCommitChanged) + if (headCommitChanged) { addHeadChangedWarning(oldState.getCommitMessage()); - else + } else { commitMessageComponent .setCommitMessage(oldState.getCommitMessage()); + commitMessageComponent + .setCaretPosition(oldState.getCaretPosition()); + } commitMessageComponent.setCommitter(oldState.getCommitter()); commitMessageComponent.setHeadCommit(getCommitId(helper .getPreviousCommit())); diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/uitext.properties b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/uitext.properties index 48d1e35ce..8c708d222 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/uitext.properties +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/uitext.properties @@ -829,6 +829,8 @@ CommitDialog_IgnoreErrors=Ignore warnings and errors CommitDialog_MessageErrors=Fix warnings/errors before you commit changes or explicitly ignore them CommitDialog_WrongTypeOfCommitMessageProvider=The CommitMessageProvider extension {0} contributed by {1} has the wrong type (it must implement org.eclipse.egit.ui.ICommitMessageProvider) CommitDialog_ErrorCreatingCommitMessageProvider=The CommitMessageProvider extension {0} contributed by {1} could not be created +CommitDialog_CaretPositionOutOfBounds=The ICommitMessageProvider2 implementation {0} provided the caret position {1}, which exceeds the length of the commit message or is negative. The caret position is ignored. +CommitDialog_IgnoreCaretPosition=Found multiple implementations of ICommitMessageProvider2. Ignoring caret positioning for {0}. SpellcheckableMessageArea_showWhitespace=Show &Whitespace Characters CommitMessageComponent_MessageInvalidAuthor=Invalid author specified. Example: A U Thor -- 2.11.4.GIT