Add "Reformat commit message" action and quick fix

This commit is contained in:
Konstantin Kolosovsky
2017-04-25 16:19:56 +03:00
parent ac0a5a3d8f
commit 8cb03871c9
5 changed files with 160 additions and 8 deletions
@@ -239,6 +239,7 @@
use-shortcut-of="Diff.ShowDiff" icon="AllIcons.Actions.Diff"/>
</group>
<action id="Vcs.ReformatCommitMessage" class="com.intellij.vcs.commit.ReformatCommitMessageAction" use-shortcut-of="ReformatCode"/>
<group id="Vcs.MessageActionGroup">
<action id="Vcs.ShowMessageHistory"
class="com.intellij.openapi.vcs.actions.ShowMessageHistoryAction"
@@ -19,6 +19,7 @@ import com.intellij.codeHighlighting.HighlightDisplayLevel;
import com.intellij.codeInsight.daemon.impl.IntentionActionFilter;
import com.intellij.codeInsight.intention.EmptyIntentionAction;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.LowPriorityAction;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.longLine.LongLineInspection;
import com.intellij.openapi.editor.Document;
@@ -34,6 +35,7 @@ import org.jetbrains.annotations.Nullable;
import static com.intellij.codeInspection.ProblemHighlightType.GENERIC_ERROR_OR_WARNING;
import static com.intellij.openapi.vcs.ui.CommitMessage.isCommitMessage;
import static com.intellij.util.ArrayUtil.isEmpty;
public abstract class BaseCommitMessageInspection extends LocalInspectionTool {
@@ -99,6 +101,19 @@ public abstract class BaseCommitMessageInspection extends LocalInspectionTool {
.createProblemDescriptor(file, exceedingRange, problemText, GENERIC_ERROR_OR_WARNING, isOnTheFly, fixes) : null;
}
public boolean canReformat(@NotNull Project project, @NotNull Document document) {
return false;
}
public void reformat(@NotNull Project project, @NotNull Document document) {
}
protected boolean hasProblems(@NotNull Project project, @NotNull Document document) {
PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(document);
return file != null && !isEmpty(checkFile(file, document, InspectionManager.getInstance(project), false));
}
@Nullable
private static Document getDocument(@NotNull PsiElement element) {
return PsiDocumentManager.getInstance(element.getProject()).getDocument(element.getContainingFile());
@@ -125,6 +140,17 @@ public abstract class BaseCommitMessageInspection extends LocalInspectionTool {
}
}
public abstract void doApplyFix(@NotNull Project project, @NotNull Document document, @NotNull ProblemDescriptor descriptor);
public abstract void doApplyFix(@NotNull Project project, @NotNull Document document, @Nullable ProblemDescriptor descriptor);
}
protected static class ReformatCommitMessageQuickFix extends BaseCommitMessageQuickFix implements LowPriorityAction {
protected ReformatCommitMessageQuickFix() {
super(ReformatCommitMessageAction.NAME);
}
@Override
public void doApplyFix(@NotNull Project project, @NotNull Document document, @Nullable ProblemDescriptor descriptor) {
ReformatCommitMessageAction.reformat(project, document);
}
}
}
@@ -65,22 +65,35 @@ public class BodyLimitInspection extends BaseCommitMessageInspection {
boolean isOnTheFly) {
return range(1, document.getLineCount())
.mapToObj(line -> checkRightMargin(file, document, manager, isOnTheFly, line, RIGHT_MARGIN,
format("Body lines should not exceed %d characters", RIGHT_MARGIN), new WrapLineQuickFix()))
format("Body lines should not exceed %d characters", RIGHT_MARGIN), new WrapLineQuickFix(),
new ReformatCommitMessageQuickFix()))
.filter(Objects::nonNull)
.toArray(ProblemDescriptor[]::new);
}
@Override
public boolean canReformat(@NotNull Project project, @NotNull Document document) {
return hasProblems(project, document);
}
@Override
public void reformat(@NotNull Project project, @NotNull Document document) {
new WrapLineQuickFix().doApplyFix(project, document, null);
}
protected class WrapLineQuickFix extends BaseCommitMessageQuickFix {
protected WrapLineQuickFix() {
super("Wrap line");
}
@Override
public void doApplyFix(@NotNull Project project, @NotNull Document document, @NotNull ProblemDescriptor descriptor) {
public void doApplyFix(@NotNull Project project, @NotNull Document document, @Nullable ProblemDescriptor descriptor) {
Editor editor = CommitMessage.getEditor(document);
if (editor != null) {
TextRange range = descriptor.getLineNumber() >= 0 ? getLineTextRange(document, descriptor.getLineNumber()) : EMPTY_RANGE;
TextRange range = descriptor != null && descriptor.getLineNumber() >= 0
? getLineTextRange(document, descriptor.getLineNumber())
: getBodyRange(document);
if (!range.isEmpty()) {
wrapLines(project, editor, document, RIGHT_MARGIN, range);
@@ -88,6 +101,11 @@ public class BodyLimitInspection extends BaseCommitMessageInspection {
}
}
@NotNull
private TextRange getBodyRange(@NotNull Document document) {
return document.getLineCount() > 1 ? TextRange.create(document.getLineStartOffset(1), document.getTextLength()) : EMPTY_RANGE;
}
private void wrapLines(@NotNull Project project,
@NotNull Editor editor,
@NotNull Document document,
@@ -0,0 +1,90 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.vcs.commit;
import com.intellij.codeInspection.ex.InspectionToolWrapper;
import com.intellij.codeInspection.ex.Tools;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.VcsDataKeys;
import com.intellij.openapi.vcs.ui.CommitMessage;
import org.jetbrains.annotations.CalledWithWriteLock;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.stream.Stream;
import static com.intellij.util.ObjectUtils.notNull;
import static com.intellij.util.ObjectUtils.tryCast;
import static java.util.stream.Collectors.toList;
public class ReformatCommitMessageAction extends DumbAwareAction {
public static final String NAME = "Reformat commit message";
public ReformatCommitMessageAction() {
setEnabledInModalContext(true);
}
@Override
public void update(@NotNull AnActionEvent e) {
Project project = e.getProject();
Document document = getCommitMessage(e);
e.getPresentation().setEnabled(project != null &&
document != null &&
getEnabledInspections(project).anyMatch(inspection -> inspection.canReformat(project, document)));
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
Project project = notNull(e.getProject());
Document document = notNull(getCommitMessage(e));
CommandProcessor.getInstance().executeCommand(project, () ->
WriteAction.run(() -> reformat(project, document)), NAME, null);
}
@CalledWithWriteLock
public static void reformat(@NotNull Project project, @NotNull Document document) {
List<BaseCommitMessageInspection> inspections = getEnabledInspections(project).collect(toList());
inspections.forEach(inspection -> inspection.reformat(project, document));
}
@Nullable
private static Document getCommitMessage(@NotNull AnActionEvent e) {
CommitMessage commitMessage = tryCast(e.getData(VcsDataKeys.COMMIT_MESSAGE_CONTROL), CommitMessage.class);
Editor editor = commitMessage != null ? commitMessage.getEditorField().getEditor() : null;
return editor != null ? editor.getDocument() : null;
}
@NotNull
private static Stream<BaseCommitMessageInspection> getEnabledInspections(@NotNull Project project) {
return CommitMessageInspectionProfile.getInstance(project).getAllEnabledInspectionTools(project).stream()
.map(Tools::getTool)
.map(InspectionToolWrapper::getTool)
.filter(BaseCommitMessageInspection.class::isInstance)
.map(BaseCommitMessageInspection.class::cast);
}
}
@@ -44,26 +44,43 @@ public class SubjectBodySeparationInspection extends BaseCommitMessageInspection
boolean isOnTheFly) {
ProblemDescriptor descriptor = document.getLineCount() > 1
? checkRightMargin(file, document, manager, isOnTheFly, 1, 0,
"Missing blank line between subject and body", new AddBlankLineQuickFix())
"Missing blank line between subject and body", new AddBlankLineQuickFix(),
new ReformatCommitMessageQuickFix())
: null;
return descriptor != null ? ar(descriptor) : null;
}
@Override
public boolean canReformat(@NotNull Project project, @NotNull Document document) {
return hasProblems(project, document);
}
@Override
public void reformat(@NotNull Project project, @NotNull Document document) {
new AddBlankLineQuickFix().doApplyFix(project, document, null);
}
protected static class AddBlankLineQuickFix extends BaseCommitMessageQuickFix {
protected AddBlankLineQuickFix() {
super("Add blank line");
}
@Override
public void doApplyFix(@NotNull Project project, @NotNull Document document, @NotNull ProblemDescriptor descriptor) {
if (descriptor.getLineNumber() >= 0) {
TextRange lineRange = getLineTextRange(document, descriptor.getLineNumber());
public void doApplyFix(@NotNull Project project, @NotNull Document document, @Nullable ProblemDescriptor descriptor) {
int line = descriptor != null && descriptor.getLineNumber() >= 0 ? descriptor.getLineNumber() : getFirstLine(document);
if (line >= 0) {
TextRange lineRange = getLineTextRange(document, line);
if (!lineRange.isEmpty()) {
document.insertString(lineRange.getStartOffset(), "\n");
}
}
}
private static int getFirstLine(@NotNull Document document) {
return document.getLineCount() > 1 ? 1 : -1;
}
}
}