Merge remote-tracking branch 'origin/master'

This commit is contained in:
Dmitry Trofimov
2015-08-20 14:04:27 +02:00
179 changed files with 7926 additions and 2406 deletions
+1
View File
@@ -65,6 +65,7 @@
<module fileurl="file://$PROJECT_DIR$/plugins/devkit/jps-plugin/devkit-jps-plugin.iml" filepath="$PROJECT_DIR$/plugins/devkit/jps-plugin/devkit-jps-plugin.iml" group="plugins" />
<module fileurl="file://$PROJECT_DIR$/platform/diff-api/diff-api.iml" filepath="$PROJECT_DIR$/platform/diff-api/diff-api.iml" group="platform" />
<module fileurl="file://$PROJECT_DIR$/platform/diff-impl/diff-impl.iml" filepath="$PROJECT_DIR$/platform/diff-impl/diff-impl.iml" group="platform" />
<module fileurl="file://$PROJECT_DIR$/platform/diff-impl/diff-tests.iml" filepath="$PROJECT_DIR$/platform/diff-impl/diff-tests.iml" group="platform" />
<module fileurl="file://$PROJECT_DIR$/xml/dom-impl/dom-impl.iml" filepath="$PROJECT_DIR$/xml/dom-impl/dom-impl.iml" group="xml" />
<module fileurl="file://$PROJECT_DIR$/xml/dom-openapi/dom-openapi.iml" filepath="$PROJECT_DIR$/xml/dom-openapi/dom-openapi.iml" group="xml" />
<module fileurl="file://$PROJECT_DIR$/xml/dom-tests/dom-tests.iml" filepath="$PROJECT_DIR$/xml/dom-tests/dom-tests.iml" group="xml" />
Binary file not shown.
Binary file not shown.
+15 -11
View File
@@ -136,18 +136,22 @@ if [ -n "$@@product_uc@@_PROPERTIES" ]; then
IDE_PROPERTIES_PROPERTY="-Didea.properties.file=$@@product_uc@@_PROPERTIES"
fi
vm_options_file="$IDE_BIN_HOME/@@vm_options@@$BITS.vmoptions"
user_vm_options_file="$HOME/.@@system_selector@@/@@vm_options@@$BITS.vmoptions"
if [ -r "$user_vm_options_file" ]; then
vm_options_file="$user_vm_options_file"
fi
if [ -n "$@@product_uc@@_VM_OPTIONS" ]; then
vm_options_file="$@@product_uc@@_VM_OPTIONS"
fi
VM_OPTIONS=""
VM_OPTIONS_FILES_USED=""
for vm_opts_file in "$IDE_BIN_HOME/@@vm_options@@$BITS.vmoptions" "$OS_SPECIFIC_BIN_DIR/@@vm_options@@$BITS.vmoptions" "$HOME/.@@system_selector@@/@@vm_options@@$BITS.vmoptions" "$@@product_uc@@_VM_OPTIONS"; do
if [ -r "$vm_opts_file" ]; then
VM_OPTIONS_DATA=`"$CAT" "$vm_opts_file" | "$GREP" -v "^#.*" | "$TR" '\n' ' '`
if [ -r "$vm_options_file" ]; then
VM_OPTIONS_DATA=`"$CAT" "$vm_options_file" | "$GREP" -v "^#.*" | "$TR" '\n' ' '`
VM_OPTIONS="$VM_OPTIONS $VM_OPTIONS_DATA"
if [ -n "$VM_OPTIONS_FILES_USED" ]; then
VM_OPTIONS_FILES_USED="$VM_OPTIONS_FILES_USED,"
fi
VM_OPTIONS_FILES_USED="$VM_OPTIONS_FILES_USED$vm_opts_file"
fi
done
else
message "Cannot find VM options file."
fi
IS_EAP="@@isEap@@"
if [ "$IS_EAP" = "true" ]; then
@@ -172,7 +176,7 @@ LD_LIBRARY_PATH="$IDE_BIN_HOME:$LD_LIBRARY_PATH" "$JDK/jre/bin/java" \
$AGENT \
"-Xbootclasspath/a:$IDE_HOME/lib/boot.jar" \
-classpath "$CLASSPATH" \
$VM_OPTIONS "-Djb.vmOptionsFile=$VM_OPTIONS_FILES_USED" \
$VM_OPTIONS "-Djb.vmOptionsFile=$vm_options_file" \
"-XX:ErrorFile=$HOME/java_error_in_@@product_uc@@_%p.log" \
-Djb.restart.code=88 -Didea.paths.selector=@@system_selector@@ \
$IDE_PROPERTIES_PROPERTY \
Binary file not shown.
@@ -17,11 +17,11 @@
package org.jetbrains.testme.instrumentation;
import org.jetbrains.org.objectweb.asm.ClassVisitor;
import org.jetbrains.org.objectweb.asm.Label;
import org.jetbrains.org.objectweb.asm.MethodVisitor;
import org.jetbrains.org.objectweb.asm.Opcodes;
public class Instrumenter extends ClassVisitor {
private static final int ADDED_CODE_STACK_SIZE = 6;
protected final ClassVisitor myClassVisitor;
private final String myClassName;
private final String myInternalClassName;
@@ -88,7 +88,7 @@ public class Instrumenter extends ClassVisitor {
mv = new StaticBlockMethodVisitor(mv);
mv.visitCode();
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(myMethodNames.length + 2, 1);
mv.visitMaxs(ADDED_CODE_STACK_SIZE, 0);
mv.visitEnd();
}
super.visitEnd();
@@ -102,12 +102,11 @@ public class Instrumenter extends ClassVisitor {
public void visitCode() {
super.visitCode();
visitLdcInsn(myClassName);
pushInstruction(this, myMethodNames.length);
visitIntInsn(Opcodes.NEWARRAY, Opcodes.T_BOOLEAN);
visitFieldInsn(Opcodes.PUTSTATIC, myInternalClassName, METHODS_VISITED, METHODS_VISITED_CLASS);
pushInstruction(this, myMethodNames.length);
visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String");
for(int i = 0; i < myMethodNames.length; ++i) {
@@ -117,28 +116,16 @@ public class Instrumenter extends ClassVisitor {
visitInsn(Opcodes.AASTORE);
}
visitVarInsn(Opcodes.ASTORE, 0);
visitMethodInsn(Opcodes.INVOKESTATIC, ProjectData.PROJECT_DATA_OWNER, "trace", "(Ljava/lang/String;[Z[Ljava/lang/String;)[Z", false);
visitFieldInsn(Opcodes.PUTSTATIC, myInternalClassName, METHODS_VISITED, METHODS_VISITED_CLASS);
Label startLabel = new Label();
visitLabel(startLabel);
visitLdcInsn(myClassName);
visitFieldInsn(Opcodes.GETSTATIC, myInternalClassName, METHODS_VISITED, METHODS_VISITED_CLASS);
visitVarInsn(Opcodes.ALOAD, 0);
visitMethodInsn(Opcodes.INVOKESTATIC, ProjectData.PROJECT_DATA_OWNER, "trace", "(Ljava/lang/String;[Z[Ljava/lang/String;)V", false);
Label endLabel = new Label();
visitLabel(endLabel);
visitLocalVariable("methodNames", "[Ljava/lang/String;", null, startLabel, endLabel, 0);
// no return here
}
public void visitMaxs(int maxStack, int maxLocals) {
final int ourMaxStack = myMethodNames.length + 2;
final int ourMaxLocals = 1;
final int ourMaxStack = ADDED_CODE_STACK_SIZE;
super.visitMaxs(Math.max(ourMaxStack, maxStack), Math.max(ourMaxLocals, maxLocals));
super.visitMaxs(Math.max(ourMaxStack, maxStack), maxLocals);
}
}
@@ -17,42 +17,31 @@ public class ProjectData {
myTraceDir = traceDir;
}
private ConcurrentMap<String, Set<String>> myTrace;
private final ConcurrentMap<String, boolean[]> myTrace2 = new ConcurrentHashMap<String, boolean[]>();
private final ConcurrentMap<String, String[]> myTrace3 = new ConcurrentHashMap<String, String[]>();
public static ProjectData getProjectData() {
return ourData;
}
public static void trace(String className, String methodSignature) {
ourData.traceLines(className, methodSignature);
}
// called from instrumented code during class's static init
public static void trace(String className, boolean[] methodFlags, String[] methodNames) {
ourData.traceLines(className, methodFlags, methodNames);
}
public void traceLines(String className, String methodSignature) {
if (myTrace != null) {
Set<String> methods = myTrace.get(className);
if (methods == null) {
methods = new HashSet<String>();
Set<String> previousMethods = myTrace.putIfAbsent(className, methods);
if (previousMethods != null) methods = previousMethods;
}
synchronized (methods) {
methods.add(methodSignature);
}
}
public static boolean[] trace(String className, boolean[] methodFlags, String[] methodNames) {
return ourData.traceLines(className, methodFlags, methodNames);
}
public synchronized void traceLines(String className, boolean[] methodFlags, String[] methodNames) {
private synchronized boolean[] traceLines(String className, boolean[] methodFlags, String[] methodNames) {
//System.out.println("Registering " + className);
assert methodFlags.length == methodNames.length;
myTrace2.put(className, methodFlags);
myTrace3.put(className, methodNames);
final boolean[] previousMethodFlags = myTrace2.putIfAbsent(className, methodFlags);
if (previousMethodFlags != null) {
assert previousMethodFlags.length == methodFlags.length;
final String[] previousMethodNames = myTrace3.get(className);
assert previousMethodNames != null && previousMethodNames.length == methodNames.length;
} else {
myTrace3.put(className, methodNames);
}
return previousMethodFlags != null ? previousMethodFlags : methodFlags;
}
private static final int STRING_LENGTH_THRESHOLD = 255;
@@ -163,22 +152,6 @@ public class ProjectData {
catch (IOException e) {
e.printStackTrace();
}
finally {
myTrace = null;
}
}
private void saveOldTrace(DataOutputStream os) throws IOException {
os.writeInt(myTrace.size());
for (Iterator<String> it = myTrace.keySet().iterator(); it.hasNext();) {
final String classData = it.next();
os.writeUTF(classData);
final Set<String> methods = myTrace.get(classData);
os.writeInt(methods.size());
for (Iterator<String> iterator = methods.iterator(); iterator.hasNext(); ) {
os.writeUTF(iterator.next());
}
}
}
public synchronized void testStarted(final String name) {
@@ -190,8 +163,4 @@ public class ProjectData {
}
}
}
private void clearOldTrace() {
myTrace = new ConcurrentHashMap<String, Set<String>>();
}
}
@@ -446,16 +446,30 @@ public class HighlightMethodUtil {
final PsiMethod containerMethod = PsiTreeUtil.getParentOfType(methodCall, PsiMethod.class, true, PsiLambdaExpression.class);
if (containerMethod != null) {
final PsiMethod method = candidate.getElement();
final List<PsiType> list = ContainerUtil.map(method.getParameterList().getParameters(),
new Function<PsiParameter, PsiType>() {
@Override
public PsiType fun(PsiParameter parameter) {
return parameter.getType();
}
});
PsiType[] leftTypes = list.toArray(new PsiType[list.size()]);
final PsiType[] expressionTypes = methodCall.getArgumentList().getExpressionTypes();
final PsiParameter[] parameters = method.getParameterList().getParameters();
final int max = Math.max(parameters.length, expressionTypes.length);
PsiType[] leftTypes = PsiType.createArray(max);
PsiType[] rightTypes = PsiType.createArray(max);
for (int i = 0; i < max; i++) {
if (i < parameters.length) {
leftTypes[i] = parameters[i].getType();
rightTypes[i] = i < expressionTypes.length ? expressionTypes[i] : null;
if (leftTypes[i] instanceof PsiEllipsisType &&
!(max == expressionTypes.length &&
expressionTypes[i] instanceof PsiArrayType &&
expressionTypes[i].getArrayDimensions() == leftTypes[i].getArrayDimensions())) {
leftTypes[i] = ((PsiEllipsisType)leftTypes[i]).getComponentType();
}
}
else {
leftTypes[i] = null;
rightTypes[i] = i < expressionTypes.length ? expressionTypes[i] : null;
}
}
final PsiSubstitutor substitutor = resolveHelper
.inferTypeArguments(method.getTypeParameters(), leftTypes, methodCall.getArgumentList().getExpressionTypes(),
.inferTypeArguments(method.getTypeParameters(), leftTypes, rightTypes,
PsiUtil.getLanguageLevel(methodCall));
PsiType methodCallTypeByArgs = substitutor.substitute(methodCall.getType());
//ensure type params are not included
@@ -227,7 +227,7 @@ public class JavaKeywordCompletion {
if (psiElement().withText(";").withSuperParent(2, PsiIfStatement.class).accepts(prevLeaf) ||
psiElement().withText("}").withSuperParent(3, PsiIfStatement.class).accepts(prevLeaf)) {
variant.consume(new OverrideableSpace(createKeyword(position, PsiKeyword.ELSE), TailTypes.SYNCHRONIZED_LPARENTH));
variant.consume(new OverrideableSpace(createKeyword(position, PsiKeyword.ELSE), TailType.HUMBLE_SPACE_BEFORE_WORD));
}
if (psiElement().withText("}").withParent(psiElement(PsiCodeBlock.class).withParent(or(psiElement(PsiTryStatement.class), psiElement(PsiCatchSection.class)))).accepts(prevLeaf)) {
@@ -58,7 +58,7 @@ public class DelegateWithDefaultParamValueIntentionAction extends PsiElementBase
if (declarationScope instanceof PsiMethod) {
final PsiMethod method = (PsiMethod)declarationScope;
final PsiClass containingClass = method.getContainingClass();
if (containingClass != null && (!containingClass.isInterface() || PsiUtil.isLanguageLevel7OrHigher(method))) {
if (containingClass != null && (!containingClass.isInterface() || PsiUtil.isLanguageLevel8OrHigher(method))) {
if (containingClass.findMethodBySignature(generateMethodPrototype(method, parameter), false) != null) {
return false;
}
@@ -971,13 +971,16 @@ public class JavaDocInfoGenerator {
if (method instanceof PsiMethod) {
PsiMethod psiMethod = (PsiMethod)method;
final PsiDocComment docComment = getDocComment(psiMethod);
final PsiDocTag[] localTags = docComment != null ? docComment.getTags() : PsiDocTag.EMPTY_ARRAY;
int parameterIndex = psiMethod.getParameterList().getParameterIndex(parameter);
final ParamInfo tagInfoProvider = findDocTag(localTags, parameterName, psiMethod, parameterLocator(parameterIndex));
PsiParameterList parameterList = psiMethod.getParameterList();
if (parameter.getParent() == parameterList) { // this can also be a parameter in foreach statement or in catch clause
final PsiDocComment docComment = getDocComment(psiMethod);
final PsiDocTag[] localTags = docComment != null ? docComment.getTags() : PsiDocTag.EMPTY_ARRAY;
int parameterIndex = parameterList.getParameterIndex(parameter);
final ParamInfo tagInfoProvider = findDocTag(localTags, parameterName, psiMethod, parameterLocator(parameterIndex));
if (tagInfoProvider != null) {
generateOneParameter(buffer, tagInfoProvider);
if (tagInfoProvider != null) {
generateOneParameter(buffer, tagInfoProvider);
}
}
}
@@ -18,6 +18,7 @@ package com.intellij.psi.util;
import com.intellij.openapi.util.Comparing;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import gnu.trove.TObjectIntHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -64,11 +65,11 @@ public class ClassUtil {
buf.append(qName);
}
else {
final PsiClass parentClass = getContainingClass(aClass);
final PsiClass parentClass = PsiTreeUtil.getContextOfType(aClass, PsiClass.class, true);
if (parentClass != null) {
formatClassName(parentClass, buf);
buf.append("$");
buf.append(getNonQualifiedClassIdx(aClass));
buf.append(getNonQualifiedClassIdx(aClass, parentClass));
final String name = aClass.getName();
if (name != null) {
buf.append(name);
@@ -77,42 +78,24 @@ public class ClassUtil {
}
}
@Nullable
private static PsiClass getContainingClass(@NotNull PsiClass aClass) {
PsiElement parent = aClass.getContext();
while (parent != null && !(parent instanceof PsiClass)) {
parent = parent.getContext();
}
return (PsiClass)parent;
}
public static int getNonQualifiedClassIdx(@NotNull final PsiClass psiClass) {
final int[] result = {-1};
final PsiClass containingClass = getContainingClass(psiClass);
if (containingClass != null) {
containingClass.accept(new JavaRecursiveElementVisitor() {
private int myCurrentIdx = 0;
private static int getNonQualifiedClassIdx(@NotNull final PsiClass psiClass, @NotNull final PsiClass containingClass) {
TObjectIntHashMap<PsiClass> indices =
CachedValuesManager.getCachedValue(containingClass, new CachedValueProvider<TObjectIntHashMap<PsiClass>>() {
@Nullable
@Override
public void visitElement(PsiElement element) {
if (result[0] == -1) {
super.visitElement(element);
}
}
@Override
public void visitClass(PsiClass aClass) {
super.visitClass(aClass);
if (aClass.getQualifiedName() == null) {
myCurrentIdx++;
if (psiClass == aClass) {
result[0] = myCurrentIdx;
public Result<TObjectIntHashMap<PsiClass>> compute() {
final TObjectIntHashMap<PsiClass> map = new TObjectIntHashMap<PsiClass>();
int index = 0;
for (PsiClass aClass : SyntaxTraverser.psiTraverser().withRoot(containingClass).postOrderDfsTraversal().filter(PsiClass.class)) {
if (aClass.getQualifiedName() == null) {
map.put(aClass, ++index);
}
}
return Result.create(map, containingClass);
}
});
}
return result[0];
return indices.get(psiClass);
}
@SuppressWarnings("unused")
@@ -1,6 +1,6 @@
public class A {
public void method() {
if (true) { foo(); }
<caret>
el<caret>
}
}
@@ -0,0 +1,6 @@
public class A {
public void method() {
if (true) { foo(); }
else <caret>
}
}
@@ -0,0 +1,12 @@
import java.util.List;
// "Make 'bar' return 'java.util.List<java.lang.String>'" "true"
public class Foo {
<T> java.util.List<T> foo(T... t) {
return null;
}
List<String> bar() {
return foo("");
}
}
@@ -0,0 +1,12 @@
import java.util.List;
// "Make 'bar' return 'java.util.List<java.lang.String>'" "true"
public class Foo {
<T> java.util.List<T> foo(T... t) {
return null;
}
List<String> bar() {
return foo("", "");
}
}
@@ -0,0 +1,12 @@
import java.util.List;
// "Make 'bar' return 'java.util.List<java.lang.String>'" "true"
public class Foo {
<T> java.util.List<T> foo(T... t) {
return null;
}
List<String> bar() {
return foo(new String[] {""});
}
}
@@ -0,0 +1,10 @@
// "Make 'bar' return 'java.util.List<java.lang.String>'" "true"
public class Foo {
<T> java.util.List<T> foo(T... t) {
return null;
}
String bar() {
return fo<caret>o("");
}
}
@@ -0,0 +1,10 @@
// "Make 'bar' return 'java.util.List<java.lang.String>'" "true"
public class Foo {
<T> java.util.List<T> foo(T... t) {
return null;
}
String bar() {
return fo<caret>o("", "");
}
}
@@ -0,0 +1,10 @@
// "Make 'bar' return 'java.util.List<java.lang.String>'" "false"
public class Foo {
<T> java.util.List<T> foo(T... t) {
return null;
}
String bar() {
return fo<caret>o();
}
}
@@ -0,0 +1,10 @@
// "Make 'bar' return 'java.util.List<java.lang.String>'" "true"
public class Foo {
<T> java.util.List<T> foo(T... t) {
return null;
}
String bar() {
return fo<caret>o(new String[] {""});
}
}
@@ -76,8 +76,8 @@ public class KeywordCompletionTest extends LightCompletionTestCase {
public void testMethodScope3() throws Exception { doTest(1, "final", "public", "static", "volatile", "abstract", "throws", "instanceof"); }
public void testMethodScope4() throws Exception { doTest(6, "final", "try", "for", "while", "return", "throw"); }
public void testMethodScope5() throws Exception { doTest(false); }
public void testElseAfterSemicolon() throws Exception { doTest(1, "else"); }
public void testElseAfterRBrace() throws Exception { doTest(1, "else"); }
public void testElseAfterSemicolon() { doTest(1, "else"); }
public void testElseAfterRBrace() { doTest(false); }
public void testExtraBracketAfterFinally1() throws Exception { doTest(false); }
public void testExtraBracketAfterFinally2() throws Exception { doTest(false); }
public void testExtendsInCastTypeParameters() throws Exception { doTest(false); }
@@ -137,7 +137,7 @@ public class KeywordCompletionTest extends LightCompletionTestCase {
assertEquals("this", myItems[1].getLookupString());
}
private void doTest(boolean select) throws Exception {
private void doTest(boolean select) {
configureByFile(BASE_PATH + "/" + getTestName(true) + ".java");
if (select) {
selectItem(myItems[0]);
@@ -1,12 +1,17 @@
/*
* Copyright (c) 2000-2006 JetBrains s.r.o. All Rights Reserved.
*/
/*
* Created by IntelliJ IDEA.
* User: Anna.Kozlova
* Date: 06-Sep-2006
* Time: 13:56:11
* Copyright 2000-2015 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.codeInsight.daemon.quickFix;
@@ -27,11 +32,6 @@ public class JavadocInspectionQuickFixTest extends LightQuickFixParameterizedTes
return new LocalInspectionTool[]{javaDocLocalInspection};
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
}
public void test() throws Exception { doAllTests(); }
@Override
@@ -33,8 +33,12 @@ public class UnscrambleDialogTest extends JavaCodeInsightFixtureTestCase {
@Override
protected void tearDown() throws Exception {
Disposer.dispose(myContent);
super.tearDown();
try {
Disposer.dispose(myContent);
}
finally {
super.tearDown();
}
}
public void testStacktrace() throws Exception {
@@ -47,9 +51,9 @@ public class UnscrambleDialogTest extends JavaCodeInsightFixtureTestCase {
public void testException() throws Exception {
showText("java.lang.NullPointerException\n" +
"\tat com.intellij.psi.css.resolve.impl.XhtmlFileInfo.findOneStyleSheet(XhtmlFileInfo.java:291)\n" +
"\tat com.intellij.psi.css.resolve.impl.XhtmlFileInfo.getStylesheets(XhtmlFileInfo.java:174)\n" +
"\tat com.intellij.psi.css.resolve.impl.XhtmlFileInfo.initStylesheets(XhtmlFileInfo.java:119)");
"\tat com.intellij.psi.css.resolve.impl.XhtmlFileInfo.findOneStyleSheet(XhtmlFileInfo.java:291)\n" +
"\tat com.intellij.psi.css.resolve.impl.XhtmlFileInfo.getStylesheets(XhtmlFileInfo.java:174)\n" +
"\tat com.intellij.psi.css.resolve.impl.XhtmlFileInfo.initStylesheets(XhtmlFileInfo.java:119)");
assertIcon("exception.png", myContent.getIcon());
assertEquals("NPE", myContent.getDisplayName());
}
@@ -16,9 +16,7 @@
package com.intellij.testFramework;
import com.intellij.ide.highlighter.ModuleFileType;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.components.ComponentsPackage;
import com.intellij.openapi.module.Module;
@@ -160,15 +158,13 @@ public abstract class ModuleTestCase extends IdeaTestCase {
}
protected final void loadModuleComponentState(@NotNull Module module, @NotNull Object component) {
AccessToken token = WriteAction.start();
ProjectEx project = (ProjectEx)myProject;
project.setOptimiseTestLoadSpeed(false);
try {
ProjectEx project = (ProjectEx)myProject;
project.setOptimiseTestLoadSpeed(false);
ComponentsPackage.getStateStore(module).initComponent(component, false);
project.setOptimiseTestLoadSpeed(true);
}
finally {
token.finish();
project.setOptimiseTestLoadSpeed(true);
}
}
+5
View File
@@ -0,0 +1,5 @@
<root>
<item name='org.junit.runner.Description T getAnnotation(java.lang.Class&lt;T&gt;)'>
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>
</root>
+18 -13
View File
@@ -375,23 +375,28 @@ NSString *getOverrideVMOptionsPath() {
}
NSArray *parseVMOptions() {
NSArray *files = @[getApplicationVMOptionsPath(),
getUserVMOptionsPath(),
getOverrideVMOptionsPath()];
NSString *vmOptionsFile = getApplicationVMOptionsPath();
NSString *userVMOptiosFile = getUserVMOptionsPath();
NSString *envVarVMOptiosFile = getOverrideVMOptionsPath();
if ([[NSFileManager defaultManager] fileExistsAtPath:userVMOptiosFile]) {
vmOptionsFile = userVMOptiosFile;
}
if ([[NSFileManager defaultManager] fileExistsAtPath:envVarVMOptiosFile]) {
vmOptionsFile = envVarVMOptiosFile;
}
NSMutableArray *options = [NSMutableArray array];
NSMutableArray *used = [NSMutableArray array];
for (NSString *file in files) {
NSLog(@"Processing VMOptions file at %@", file);
NSArray *contents = [VMOptionsReader readFile:file];
if (contents != nil) {
NSLog(@"Done");
[used addObject:file];
[options addObjectsFromArray:contents];
} else {
NSLog(@"No content found");
}
NSLog(@"Processing VMOptions file at %@", vmOptionsFile);
NSArray *contents = [VMOptionsReader readFile:vmOptionsFile];
if (contents != nil) {
NSLog(@"Done");
[used addObject:vmOptionsFile];
[options addObjectsFromArray:contents];
} else {
NSLog(@"No content found at %@ ", vmOptionsFile);
}
[options addObject:[NSString stringWithFormat:@"-Djb.vmOptionsFile=%@", [used componentsJoinedByString:@","]]];
+35 -23
View File
@@ -374,6 +374,29 @@ bool AddClassPathOptions(std::vector<std::string>& vmOptionLines)
return true;
}
//return VMOptions from one of the files in the following order:
//$<IDE-NAME>_VM_OPTIONS
//$CONFIG_DIRECTORY/<ide-name>[64][.exe].vmoptions
//bin/<ide-name>[64][.exe].vmoptions
bool FindValidVMOptions(std::vector<std::wstring> files, std::wstring& used, std::vector<std::string>& vmOptionLines)
{
if (files.size() != 0)
{
for (int i = 0; i < files.size(); i++)
{
if (GetFileAttributes(files[i].c_str()) != INVALID_FILE_ATTRIBUTES)
{
if (LoadVMOptionsFile(files[i].c_str(), vmOptionLines))
{
used += (used.size() ? L"," : L"") + files[i];
return true;
}
}
}
}
return false;
}
void AddPredefinedVMOptions(std::vector<std::string>& vmOptionLines)
{
std::string vmOptions = LoadStdString(IDS_VM_OPTIONS);
@@ -403,8 +426,13 @@ bool LoadVMOptions()
GetModuleFileName(NULL, buffer, _MAX_PATH);
std::wstring module(buffer);
files.push_back(module + L".vmoptions");
if (LoadString(hInst, IDS_VM_OPTIONS_ENV_VAR, buffer, _MAX_PATH))
{
if (GetEnvironmentVariableW(buffer, copy, _MAX_PATH)) {
ExpandEnvironmentStrings(copy, buffer, _MAX_PATH);
files.push_back(std::wstring(buffer));
}
}
if (LoadString(hInst, IDS_VM_OPTIONS_PATH, buffer, _MAX_PATH))
{
@@ -413,33 +441,17 @@ bool LoadVMOptions()
files.push_back(selector + module.substr(module.find_last_of('\\')) + L".vmoptions");
}
if (LoadString(hInst, IDS_VM_OPTIONS_ENV_VAR, buffer, _MAX_PATH))
{
if (GetEnvironmentVariableW(buffer, copy, _MAX_PATH)) {
ExpandEnvironmentStrings(copy, buffer, _MAX_PATH);
files.push_back(std::wstring(buffer));
}
}
files.push_back(module + L".vmoptions");
std::wstring used;
std::vector<std::string> vmOptionLines;
if (files.size() == 0) {
if (!FindValidVMOptions(files, used, vmOptionLines))
{
std::string error = LoadStdString(IDS_ERROR_LAUNCHING_APP);
MessageBoxA(NULL, "Cannot find VM options file", error.c_str(), MB_OK);
return false;
}
std::wstring used;
std::vector<std::string> vmOptionLines;
for (int i = 0; i < files.size(); i++)
{
if (GetFileAttributes(files[i].c_str()) != INVALID_FILE_ATTRIBUTES)
{
if (LoadVMOptionsFile(files[i].c_str(), vmOptionLines))
{
used += (used.size() ? L"," : L"") + files[i];
}
}
}
vmOptionLines.push_back(std::string("-Djb.vmOptions=") + EncodeWideACP(used));
if (!AddClassPathOptions(vmOptionLines)) return false;
@@ -45,7 +45,7 @@ abstract class RefEntityImpl implements RefEntity {
RefEntityImpl(@NotNull String name, @NotNull RefManager manager) {
myManager = (RefManagerImpl)manager;
myName = name;
myName = myManager.internName(name);
}
@NotNull
@@ -54,6 +54,7 @@ import com.intellij.psi.impl.light.LightElement;
import com.intellij.util.ConcurrencyUtil;
import com.intellij.util.Consumer;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.StringInterner;
import gnu.trove.THashMap;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
@@ -85,6 +86,7 @@ public class RefManagerImpl extends RefManager {
private final Map<Key, RefManagerExtension> myExtensions = new THashMap<Key, RefManagerExtension>();
private final Map<Language, RefManagerExtension> myLanguageExtensions = new HashMap<Language, RefManagerExtension>();
private final StringInterner myNameInterner = new StringInterner();
public RefManagerImpl(@NotNull Project project, @Nullable AnalysisScope scope, @NotNull GlobalInspectionContext context) {
myProject = project;
@@ -106,6 +108,12 @@ public class RefManagerImpl extends RefManager {
}
}
public String internName(@NotNull String name) {
synchronized (myNameInterner) {
return myNameInterner.intern(name);
}
}
@NotNull
public GlobalInspectionContext getContext() {
return myContext;
@@ -76,6 +76,4 @@ class ApplicationStoreImpl(private val application: ApplicationImpl, pathMacroMa
}
}
}
override fun getMessageBus() = application.getMessageBus()
}
@@ -19,6 +19,7 @@ import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.PathManager
import com.intellij.openapi.application.WriteAction
import com.intellij.openapi.application.ex.DecodeDefaultsUtil
import com.intellij.openapi.application.runBatchUpdate
import com.intellij.openapi.components.*
import com.intellij.openapi.components.StateStorage.SaveSession
import com.intellij.openapi.components.StateStorageChooserEx.Resolution
@@ -53,6 +54,9 @@ import java.util.concurrent.CopyOnWriteArrayList
private val LOG = Logger.getInstance(javaClass<ComponentStoreImpl>())
/**
* <b>Note:</b> this class is used in upsource, please notify upsource team in case you change its API.
*/
public abstract class ComponentStoreImpl : IComponentStore {
private val myComponents = Collections.synchronizedMap(THashMap<String, Any>())
private val mySettingsSavingComponents = CopyOnWriteArrayList<SettingsSavingComponent>()
@@ -346,8 +350,10 @@ public abstract class ComponentStoreImpl : IComponentStore {
return notReloadableComponents ?: emptySet<String>()
}
override final fun reloadStates(componentNames: MutableSet<String>) {
reinitComponents(componentNames)
override final fun reloadStates(componentNames: MutableSet<String>, messageBus: MessageBus) {
runBatchUpdate(messageBus) {
reinitComponents(componentNames)
}
}
override final fun reloadState(componentClass: Class<out PersistentStateComponent<*>>) {
@@ -370,8 +376,6 @@ public abstract class ComponentStoreImpl : IComponentStore {
}
}
protected abstract fun getMessageBus(): MessageBus
/**
* null if reloaded
* empty list if nothing to reload
@@ -404,19 +408,15 @@ public abstract class ComponentStoreImpl : IComponentStore {
}
// used in settings repository plugin
/**
* You must call it in batch mode (use runBatchUpdate)
*/
public fun reinitComponents(componentNames: Set<String>, changedStorages: Set<StateStorage> = emptySet(), notReloadableComponents: Collection<String> = emptySet()) {
val messageBus = getMessageBus()
messageBus.syncPublisher(BatchUpdateListener.TOPIC).onBatchUpdateStarted()
try {
for (componentName in componentNames) {
if (!notReloadableComponents.contains(componentName)) {
reloadState(componentName, changedStorages)
}
for (componentName in componentNames) {
if (!notReloadableComponents.contains(componentName)) {
reloadState(componentName, changedStorages)
}
}
finally {
messageBus.syncPublisher(BatchUpdateListener.TOPIC).onBatchUpdateFinished()
}
}
companion object {
@@ -85,9 +85,7 @@ class DefaultProjectStoreImpl(override val project: ProjectImpl, private val pat
// don't want to optimize and use already loaded data - it will add unnecessary complexity and implementation-lock (currently we store loaded archived state in memory, but later implementation can be changed)
fun getStateCopy() = storage.loadLocalData()
override fun getMessageBus() = project.getMessageBus()
override final fun getPathMacroManagerForDefaults() = pathMacroManager
override fun selectDefaultStorages(storages: Array<Storage>, operation: StateStorageOperation) = selectDefaultStorages(storages, operation, StorageScheme.DEFAULT)
@@ -18,22 +18,18 @@ package com.intellij.configurationStore
import com.intellij.openapi.components.PathMacroManager
import com.intellij.openapi.components.StoragePathMacros
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ex.ProjectEx
class ModuleStoreImpl(private val module: Module, private val pathMacroManager: PathMacroManager) : ComponentStoreImpl() {
override val storageManager = ModuleStateStorageManager(pathMacroManager.createTrackingSubstitutor(), module)
class ModuleStoreImpl(module: Module, private val pathMacroManager: PathMacroManager) : ComponentStoreImpl() {
override val project = module.getProject()
override val project: Project?
get() = module.getProject()
override val storageManager = ModuleStateStorageManager(pathMacroManager.createTrackingSubstitutor(), module)
override fun setPath(path: String) {
storageManager.addMacro(StoragePathMacros.MODULE_FILE, path)
}
override fun optimizeTestLoading() = (module.getProject() as ProjectEx).isOptimiseTestLoadSpeed()
override fun getMessageBus() = module.getMessageBus()
override fun optimizeTestLoading() = (project as ProjectEx).isOptimiseTestLoadSpeed()
override final fun getPathMacroManagerForDefaults() = pathMacroManager
}
@@ -247,8 +247,6 @@ open class ProjectStoreImpl(override val project: ProjectImpl, private val pathM
protected open fun beforeSave(readonlyFiles: List<Pair<SaveSession, VirtualFile>>) {
}
override fun getMessageBus() = project.getMessageBus()
override fun <T> getStorageSpecs(component: PersistentStateComponent<T>, stateSpec: State, operation: StateStorageOperation): Array<Storage> {
// if we create project from default, component state written not to own storage file, but to project file,
// we don't have time to fix it properly, so, ancient hack restored.
@@ -46,6 +46,8 @@ import kotlin.reflect.jvm.java
/**
* If componentManager not specified, storage will not add file tracker (see VirtualFileTracker)
* <p/>
* <b>Note:</b> this class is used in upsource, please notify upsource team in case you change its API.
*/
open class StateStorageManagerImpl(private val rootTagName: String,
private val pathMacroSubstitutor: TrackingPathMacroSubstitutor? = null,
@@ -18,8 +18,8 @@ package com.intellij.configurationStore
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ApplicationNamesInfo
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.application.WriteAction
import com.intellij.openapi.application.ex.ApplicationManagerEx
import com.intellij.openapi.application.runBatchUpdate
import com.intellij.openapi.components.ComponentManager
import com.intellij.openapi.components.StateStorage
import com.intellij.openapi.components.impl.stores.*
@@ -65,10 +65,12 @@ class StoreAwareProjectManager(virtualFileManager: VirtualFileManager, progressM
val changes = CHANGED_FILES_KEY.get(project) ?: continue
CHANGED_FILES_KEY.set(project, null)
if (!changes.isEmpty()) {
for ((store, storages) in changes.entrySet()) {
@suppress("UNCHECKED_CAST")
if (reloadStore(storages as Set<StateStorage>, store, false) == ReloadComponentStoreStatus.RESTART_AGREED) {
projectsToReload.add(project)
runBatchUpdate(project.getMessageBus()) {
for ((store, storages) in changes.entrySet()) {
@suppress("UNCHECKED_CAST")
if (reloadStore(storages as Set<StateStorage>, store, false) == ReloadComponentStoreStatus.RESTART_AGREED) {
projectsToReload.add(project)
}
}
}
}
@@ -216,7 +218,6 @@ private fun reloadStore(changedStorages: Set<StateStorage>, store: ComponentStor
val notReloadableComponents: Collection<String>?
var willBeReloaded = false
try {
val token = WriteAction.start()
try {
notReloadableComponents = store.reload(changedStorages)
}
@@ -225,9 +226,6 @@ private fun reloadStore(changedStorages: Set<StateStorage>, store: ComponentStor
Messages.showWarningDialog(ProjectBundle.message("project.reload.failed", e.getMessage()), ProjectBundle.message("project.reload.failed.title"))
return ReloadComponentStoreStatus.ERROR
}
finally {
token.finish()
}
if (notReloadableComponents == null || notReloadableComponents.isEmpty()) {
return ReloadComponentStoreStatus.SUCCESS
@@ -16,7 +16,6 @@
package com.intellij.configurationStore
import com.intellij.application.options.PathMacrosImpl
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.components.*
import com.intellij.openapi.components.impl.stores.StreamProvider
@@ -149,8 +148,6 @@ class ApplicationStoreTest {
override fun setPath(path: String) {
storageManager.addMacro(StoragePathMacros.APP_CONFIG, path)
}
override fun getMessageBus() = ApplicationManager.getApplication().getMessageBus()
}
abstract class Foo {
@@ -1,23 +1,30 @@
package com.intellij.configurationStore
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.components.StoragePathMacros
import com.intellij.openapi.components.impl.stores.BatchUpdateListener
import com.intellij.openapi.components.stateStore
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.module.ModuleTypeId
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ex.ProjectEx
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.openapi.roots.impl.storage.ClasspathStorage
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.testFramework.*
import gnu.trove.TObjectIntHashMap
import org.assertj.core.api.Assertions.assertThat
import org.junit.ClassRule
import org.junit.Rule
import org.junit.Test
import java.nio.file.Path
import java.nio.file.Paths
@RunsInEdt class ModuleStoreTest {
@RunsInEdt
@RunsInActiveStoreMode
class ModuleStoreTest {
companion object {
ClassRule val projectRule = ProjectRule()
@@ -39,7 +46,7 @@ import java.nio.file.Path
private val tempDirManager = TemporaryDirectory()
private val ruleChain = RuleChain(tempDirManager, EdtRule())
private val ruleChain = RuleChain(tempDirManager, EdtRule(), ActiveStoreRule(projectRule), DisposeModulesRule(projectRule))
public Rule fun getChain(): RuleChain = ruleChain
@@ -49,53 +56,107 @@ import java.nio.file.Path
"<module type=\"JAVA_MODULE\" foo=\"bar\" version=\"4\" />")
}
projectRule.project.runInStoreLoadMode {
moduleFile.loadModule().useAndDispose {
assertThat(getOptionValue("foo")).isEqualTo("bar")
moduleFile.loadModule().useAndDispose {
assertThat(getOptionValue("foo")).isEqualTo("bar")
setOption("foo", "not bar")
saveStore()
}
setOption("foo", "not bar")
saveStore()
}
moduleFile.loadModule().useAndDispose {
assertThat(getOptionValue("foo")).isEqualTo("not bar")
moduleFile.loadModule().useAndDispose {
assertThat(getOptionValue("foo")).isEqualTo("not bar")
setOption("foo", "not bar")
saveStore()
}
setOption("foo", "not bar")
saveStore()
}
}
@Test fun `must be empty if classpath storage`() {
// we must not use VFS here, file must not be created
val moduleFile = tempDirManager.newPath("module").resolve("test.iml")
projectRule.project.runInStoreLoadMode {
moduleFile.createModule().useAndDispose {
ModuleRootModificationUtil.addContentRoot(this, moduleFile.parentSystemIndependentPath)
saveStore()
assertThat(moduleFile).isRegularFile()
assertThat(moduleFile.readText()).startsWith("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module type=\"JAVA_MODULE\" version=\"4\">")
moduleFile.createModule().useAndDispose {
ModuleRootModificationUtil.addContentRoot(this, moduleFile.parentSystemIndependentPath)
saveStore()
assertThat(moduleFile).isRegularFile()
assertThat(moduleFile.readText()).startsWith("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module type=\"JAVA_MODULE\" version=\"4\">")
ClasspathStorage.setStorageType(ModuleRootManager.getInstance(this), "eclipse")
saveStore()
assertThat(moduleFile).hasContent("""<?xml version="1.0" encoding="UTF-8"?>
ClasspathStorage.setStorageType(ModuleRootManager.getInstance(this), "eclipse")
saveStore()
assertThat(moduleFile).hasContent("""<?xml version="1.0" encoding="UTF-8"?>
<module classpath="eclipse" classpath-dir="$MODULE_DIR" type="JAVA_MODULE" version="4" />""")
}
}
@Test fun `one batch update session if several modules changed`() {
val nameToCount = TObjectIntHashMap<String>()
val root = tempDirManager.newPath()
fun Module.addContentRoot() {
val moduleName = getName()
var batchUpdateCount = 0
nameToCount.put(moduleName, batchUpdateCount)
getMessageBus().connect().subscribe(BatchUpdateListener.TOPIC, object : BatchUpdateListener {
override fun onBatchUpdateStarted() {
nameToCount.put(moduleName, ++batchUpdateCount)
}
override fun onBatchUpdateFinished() {
}
})
//
ModuleRootModificationUtil.addContentRoot(this, root.resolve(moduleName).systemIndependentPath)
assertThat(contentRootUrls).hasSize(1)
saveStore()
}
fun Module.removeContentRoot() {
val modulePath = stateStore.getStateStorageManager().expandMacros(StoragePathMacros.MODULE_FILE)
val moduleFile = Paths.get(modulePath)
assertThat(moduleFile).isRegularFile()
val virtualFile = LocalFileSystem.getInstance().findFileByPath(modulePath)!!
val newData = moduleFile.readText().replace("<content url=\"file://\$MODULE_DIR$/${getName()}\" />\n", "").toByteArray()
runWriteAction {
virtualFile.setBinaryContent(newData)
}
}
fun Module.assertChangesApplied() {
assertThat(contentRootUrls).isEmpty()
}
val m1 = root.resolve("m1.iml").createModule()
val m2 = root.resolve("m2.iml").createModule()
var projectBatchUpdateCount = 0
projectRule.project.getMessageBus().connect(m1).subscribe(BatchUpdateListener.TOPIC, object : BatchUpdateListener {
override fun onBatchUpdateStarted() {
nameToCount.put("p", ++projectBatchUpdateCount)
}
override fun onBatchUpdateFinished() {
}
})
m1.addContentRoot()
m2.addContentRoot()
m1.removeContentRoot()
m2.removeContentRoot()
(ProjectManager.getInstance() as StoreAwareProjectManager).flushChangedAlarm()
m1.assertChangesApplied()
m2.assertChangesApplied()
assertThat(nameToCount.size()).isEqualTo(3)
assertThat(nameToCount.get("p")).isEqualTo(1)
assertThat(nameToCount.get("m1")).isEqualTo(1)
assertThat(nameToCount.get("m1")).isEqualTo(1)
}
}
inline fun <T> Project.runInStoreLoadMode(task: () -> T): T {
val isModeDisabled = (this as ProjectEx).isOptimiseTestLoadSpeed()
if (isModeDisabled) {
setOptimiseTestLoadSpeed(false)
}
try {
return task()
}
finally {
if (isModeDisabled) {
setOptimiseTestLoadSpeed(true)
}
}
}
val Module.contentRootUrls: Array<String>
get() = ModuleRootManager.getInstance(this).getContentRootUrls()
@@ -486,23 +486,48 @@ public class PluginManagerCore {
return 0;
}
@NotNull
private static Collection<URL> getClassLoaderUrls() {
Collection<URL> urls = null;
final ClassLoader classLoader = PluginManagerCore.class.getClassLoader();
final Class<? extends ClassLoader> aClass = classLoader.getClass();
try {
@SuppressWarnings("unchecked") List<URL> urls = (List<URL>)aClass.getMethod("getUrls").invoke(classLoader);
return urls;
//noinspection unchecked
urls = (List<URL>)aClass.getMethod("getUrls").invoke(classLoader);
}
catch (IllegalAccessException ignored) { }
catch (InvocationTargetException ignored) { }
catch (NoSuchMethodException ignored) { }
if (classLoader instanceof URLClassLoader) {
return Arrays.asList(((URLClassLoader)classLoader).getURLs());
if (urls == null && classLoader instanceof URLClassLoader) {
urls = Arrays.asList(((URLClassLoader)classLoader).getURLs());
}
return Collections.emptyList();
return urls == null ? Collections.<URL>emptyList() : getClassLoaderUrls(urls);
}
public static Collection<URL> getClassLoaderUrls(Collection<URL> urls) {
List<URL> additionalRefs = new ArrayList<URL>();
for (URL url : urls) {
if ("file".equals(url.getProtocol())) {
File file = new File(decodeUrl(url.getFile()));
final String[] referencedJars = ClassPath.loadManifestClasspath(file);
if (referencedJars != null) {
for (String referencedJar : referencedJars) {
try {
additionalRefs.add(new URL(referencedJar));
}
catch (Exception ignore) {}
}
}
}
}
if (!additionalRefs.isEmpty()) {
Collection<URL> result = new ArrayList<URL>(urls);
result.addAll(additionalRefs);
return result;
}
return urls;
}
private static void prepareLoadingPluginsErrorMessage(@NotNull List<String> errors) {
@@ -907,21 +932,6 @@ public class PluginManagerCore {
if (progress != null && !pluginDescriptor.getName().equals(SPECIAL_IDEA_PLUGIN)) {
progress.showProgress("Plugin loaded: " + pluginDescriptor.getName(), PLUGINS_PROGRESS_MAX_VALUE * (float)i / urls.size());
}
continue;
}
final String[] referencedJars = ClassPath.loadManifestClasspath(file);
if (referencedJars != null) {
for (String referencedJar : referencedJars) {
final File referencedFile;
try {
referencedFile = new File(new URI(referencedJar));
}
catch (Exception e) {
continue;
}
loadDescriptorFromClassPath(referencedFile, result, platformPrefix);
}
}
}
}
@@ -18,6 +18,7 @@ package com.intellij.diff;
import com.intellij.diff.contents.DiffContent;
import com.intellij.diff.contents.DocumentContent;
import com.intellij.diff.contents.EmptyContent;
import com.intellij.diff.contents.FileContent;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileTypes.FileType;
@@ -64,6 +65,9 @@ public abstract class DiffContentFactory {
@Nullable
public abstract DocumentContent createDocument(@Nullable Project project, @NotNull VirtualFile file);
@Nullable
public abstract FileContent createFile(@Nullable Project project, @NotNull VirtualFile file);
@NotNull
public abstract DiffContent createClipboardContent();
@@ -16,9 +16,11 @@
package com.intellij.diff;
import com.intellij.openapi.ui.WindowWrapper;
import com.intellij.openapi.util.BooleanGetter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
public class DiffDialogHints {
@@ -32,7 +34,9 @@ public class DiffDialogHints {
//
@Nullable private final WindowWrapper.Mode myMode;
@Nullable private final Component myParent;
@Nullable private Component myParent;
@Nullable private BooleanGetter myOkAction;
@Nullable private BooleanGetter myCancelAction;
public DiffDialogHints(@Nullable WindowWrapper.Mode mode) {
this(mode, null);
@@ -43,6 +47,18 @@ public class DiffDialogHints {
myParent = parent;
}
public void setParent(@Nullable Component value) {
myParent = value;
}
public void setOkAction(@Nullable BooleanGetter value) {
myOkAction = value;
}
public void setCancelAction(@Nullable BooleanGetter value) {
myCancelAction = value;
}
//
// Getters
//
@@ -56,4 +72,14 @@ public class DiffDialogHints {
public Component getParent() {
return myParent;
}
@Nullable
public BooleanGetter getOkAction() {
return myOkAction;
}
@Nullable
public BooleanGetter getCancelAction() {
return myCancelAction;
}
}
@@ -16,6 +16,7 @@
package com.intellij.diff;
import com.intellij.diff.chains.DiffRequestChain;
import com.intellij.diff.merge.MergeRequest;
import com.intellij.diff.requests.DiffRequest;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.components.ServiceManager;
@@ -47,4 +48,7 @@ public abstract class DiffManager {
@NotNull
public abstract DiffRequestPanel createRequestPanel(@Nullable Project project, @NotNull Disposable parent, @Nullable Window window);
@CalledInAwt
public abstract void showMerge(@Nullable Project project, @NotNull MergeRequest request);
}
@@ -16,6 +16,8 @@
package com.intellij.diff;
import com.intellij.diff.chains.DiffRequestChain;
import com.intellij.diff.merge.MergeRequest;
import com.intellij.diff.merge.MergeTool;
import com.intellij.diff.requests.DiffRequest;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.project.Project;
@@ -45,6 +47,12 @@ public abstract class DiffManagerEx extends DiffManager {
@CalledInAwt
public abstract void showDiffBuiltin(@Nullable Project project, @NotNull DiffRequestChain requests, @NotNull DiffDialogHints hints);
@CalledInAwt
public abstract void showMergeBuiltin(@Nullable Project project, @NotNull MergeRequest request);
@NotNull
public abstract List<DiffTool> getDiffTools();
@NotNull
public abstract List<MergeTool> getMergeTools();
}
@@ -15,13 +15,21 @@
*/
package com.intellij.diff;
import com.intellij.diff.merge.MergeRequest;
import com.intellij.diff.merge.MergeResult;
import com.intellij.diff.merge.TextMergeRequest;
import com.intellij.diff.requests.ContentDiffRequest;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Consumer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/*
* Use ProgressManager.executeProcessUnderProgress() to pass modality state if needed
*/
@@ -31,12 +39,19 @@ public abstract class DiffRequestFactory {
return ServiceManager.getService(DiffRequestFactory.class);
}
//
// Diff
//
@NotNull
public abstract ContentDiffRequest createFromFiles(@Nullable Project project, @NotNull VirtualFile file1, @NotNull VirtualFile file2);
@NotNull
public abstract ContentDiffRequest createClipboardVsValue(@NotNull String value);
//
// Titles
//
@NotNull
public abstract String getContentTitle(@NotNull VirtualFile file);
@@ -46,4 +61,63 @@ public abstract class DiffRequestFactory {
@NotNull
public abstract String getTitle(@NotNull VirtualFile file);
//
// Merge
//
@NotNull
public abstract MergeRequest createMergeRequest(@Nullable Project project,
@Nullable FileType fileType,
@NotNull Document output,
@NotNull List<String> textContents,
@Nullable String title,
@NotNull List<String> titles,
@Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException;
@NotNull
public abstract MergeRequest createMergeRequest(@Nullable Project project,
@NotNull VirtualFile output,
@NotNull List<byte[]> byteContents,
@Nullable String title,
@NotNull List<String> contentTitles,
@Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException;
@NotNull
public abstract TextMergeRequest createTextMergeRequest(@Nullable Project project,
@NotNull VirtualFile output,
@NotNull List<byte[]> byteContents,
@Nullable String title,
@NotNull List<String> contentTitles,
@Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException;
@NotNull
public abstract MergeRequest createBinaryMergeRequest(@Nullable Project project,
@NotNull VirtualFile output,
@NotNull List<byte[]> byteContents,
@Nullable String title,
@NotNull List<String> contentTitles,
@Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException;
@NotNull
public abstract MergeRequest createMergeRequestFromFiles(@Nullable Project project,
@NotNull VirtualFile output,
@NotNull List<VirtualFile> contents,
@Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException;
@NotNull
public abstract MergeRequest createMergeRequestFromFiles(@Nullable Project project,
@NotNull VirtualFile output,
@NotNull List<VirtualFile> contents,
@Nullable String title,
@NotNull List<String> contentTitles,
@Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException;
@NotNull
public abstract TextMergeRequest createTextMergeRequestFromFiles(@Nullable Project project,
@NotNull VirtualFile output,
@NotNull List<VirtualFile> contents,
@Nullable String title,
@NotNull List<String> contentTitles,
@Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException;
}
@@ -0,0 +1,30 @@
/*
* Copyright 2000-2015 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.diff;
public class InvalidDiffRequestException extends Exception {
public InvalidDiffRequestException(String message) {
super(message);
}
public InvalidDiffRequestException(String message, Throwable cause) {
super(message, cause);
}
public InvalidDiffRequestException(Throwable cause) {
super(cause);
}
}
@@ -26,6 +26,12 @@ public interface DiffRequestProducer {
@NotNull
String getName();
/*
* Should be called either in EDT or without ReadLock.
* Some implementors might need WriteLock, so usage of Application.invokeAndWait() is possible.
*
* Valid ModalityState should be passed with ProgressIndicator.getModalityState().
*/
@CalledInBackground
@NotNull
DiffRequest process(@NotNull UserDataHolder context, @NotNull ProgressIndicator indicator)
@@ -0,0 +1,49 @@
/*
* Copyright 2000-2015 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.diff.merge;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.UserDataHolder;
import com.intellij.openapi.util.UserDataHolderBase;
import org.jetbrains.annotations.CalledInAwt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public abstract class MergeContext implements UserDataHolder {
protected final UserDataHolderBase myUserDataHolder = new UserDataHolderBase();
@Nullable
public abstract Project getProject();
public abstract boolean isFocused();
public abstract void requestFocus();
@CalledInAwt
public abstract void finishMerge(@NotNull MergeResult result);
@Nullable
@Override
public <T> T getUserData(@NotNull Key<T> key) {
return myUserDataHolder.getUserData(key);
}
@Override
public <T> void putUserData(@NotNull Key<T> key, @Nullable T value) {
myUserDataHolder.putUserData(key, value);
}
}
@@ -0,0 +1,47 @@
/*
* Copyright 2000-2015 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.diff.merge;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.UserDataHolder;
import com.intellij.openapi.util.UserDataHolderBase;
import org.jetbrains.annotations.CalledInAwt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public abstract class MergeRequest implements UserDataHolder {
protected final UserDataHolderBase myUserDataHolder = new UserDataHolderBase();
@Nullable
public abstract String getTitle();
/*
* Called on conflict resolve end.
*/
@CalledInAwt
public abstract void applyResult(@NotNull MergeResult result);
@Nullable
@Override
public <T> T getUserData(@NotNull Key<T> key) {
return myUserDataHolder.getUserData(key);
}
@Override
public <T> void putUserData(@NotNull Key<T> key, @Nullable T value) {
myUserDataHolder.putUserData(key, value);
}
}
@@ -0,0 +1,18 @@
/*
* Copyright 2000-2015 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.diff.merge;
public enum MergeResult {CANCEL, LEFT, RIGHT, RESOLVED}
@@ -0,0 +1,66 @@
/*
* Copyright 2000-2015 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.diff.merge;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.util.BooleanGetter;
import org.jetbrains.annotations.CalledInAwt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.List;
public interface MergeTool {
ExtensionPointName<MergeTool> EP_NAME = ExtensionPointName.create("com.intellij.diff.merge.MergeTool");
@CalledInAwt
@NotNull
MergeViewer createComponent(@NotNull MergeContext context, @NotNull MergeRequest request);
boolean canShow(@NotNull MergeContext context, @NotNull MergeRequest request);
/*
* Merge viewer should call MergeContext.finishMerge(MergeResult) when processing is over.
*
* MergeRequest.applyResult() will be performed by the caller, so it shouldn't be called by MergeViewer directly.
*/
interface MergeViewer extends Disposable {
@NotNull
JComponent getComponent();
@Nullable
JComponent getPreferredFocusedComponent();
@Nullable
Action getResolveAction(@NotNull MergeResult result);
@CalledInAwt
ToolbarComponents init();
@Override
@CalledInAwt
void dispose();
}
class ToolbarComponents {
@Nullable public List<AnAction> toolbarActions;
@Nullable public JComponent statusPanel;
@Nullable public BooleanGetter closeHandler; // return false if merge window should be prevented from closing and canceling resolve.
}
}
@@ -0,0 +1,31 @@
/*
* Copyright 2000-2015 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.diff.merge;
import com.intellij.diff.contents.DocumentContent;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public abstract class TextMergeRequest extends ThreesideMergeRequest {
@Override
@NotNull
public abstract List<DocumentContent> getContents();
@Override
@NotNull
public abstract DocumentContent getOutputContent();
}
@@ -0,0 +1,40 @@
/*
* Copyright 2000-2015 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.diff.merge;
import com.intellij.diff.contents.DiffContent;
import org.jetbrains.annotations.CalledInAwt;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public abstract class ThreesideMergeRequest extends MergeRequest {
/*
* 3 contents: left - middle - right (local - base - server)
*/
@NotNull
public abstract List<? extends DiffContent> getContents();
@NotNull
public abstract DiffContent getOutputContent();
/**
* @return contents names. Should have same length as {@link #getContents()}
* Titles could be null.
*/
@NotNull
public abstract List<String> getContentTitles();
}
@@ -0,0 +1,26 @@
/*
* Copyright 2000-2015 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.diff.requests;
import com.intellij.diff.DiffContext;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
public abstract class ComponentDiffRequest extends DiffRequest {
@NotNull
public abstract JComponent getComponent(@NotNull DiffContext context);
}
@@ -20,6 +20,7 @@ import com.intellij.openapi.actionSystem.DataProvider;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import javax.swing.*;
import java.util.List;
public interface DiffUserDataKeys {
@@ -52,6 +53,7 @@ public interface DiffUserDataKeys {
// Both data from DiffContext / DiffRequest will be used. Data from DiffRequest will be used first.
//
Key<List<JComponent>> NOTIFICATIONS = Key.create("Diff.Notifications");
Key<List<AnAction>> CONTEXT_ACTIONS = Key.create("Diff.ContextActions");
Key<DataProvider> DATA_PROVIDER = Key.create("Diff.DataProvider");
Key<Boolean> GO_TO_SOURCE_DISABLE = Key.create("Diff.GoToSourceDisable");
@@ -34,6 +34,13 @@ public enum Side {
myIndex = index;
}
@NotNull
public static Side fromIndex(int index) {
if (index == 0) return LEFT;
if (index == 1) return RIGHT;
throw new IndexOutOfBoundsException("index: " + index);
}
@NotNull
public static Side fromLeft(boolean isLeft) {
return isLeft ? LEFT : RIGHT;
@@ -32,6 +32,14 @@ public enum ThreeSide {
myIndex = index;
}
@NotNull
public static ThreeSide fromIndex(int index) {
if (index == 0) return LEFT;
if (index == 1) return BASE;
if (index == 2) return RIGHT;
throw new IndexOutOfBoundsException("index: " + index);
}
public int getIndex() {
return myIndex;
}
+1 -3
View File
@@ -2,15 +2,13 @@
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<content url="file://$MODULE_DIR$/src">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="core-api" />
<orderEntry type="module" module-name="diff-api" exported="" />
<orderEntry type="module" module-name="testFramework" scope="TEST" />
<orderEntry type="module" module-name="platform-api" />
<orderEntry type="module" module-name="platform-impl" />
<orderEntry type="module" module-name="vcs-api-core" />
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$/tests">
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="core-api" />
<orderEntry type="module" module-name="diff-api" exported="" />
<orderEntry type="module" module-name="testFramework" scope="TEST" />
<orderEntry type="module" module-name="platform-api" />
<orderEntry type="module" module-name="platform-impl" />
<orderEntry type="module" module-name="vcs-api-core" />
<orderEntry type="module" module-name="diff-impl" scope="TEST" />
</component>
</module>
@@ -17,7 +17,6 @@ package com.intellij.diff;
import com.intellij.diff.contents.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.EditorFactory;
@@ -119,6 +118,13 @@ public class DiffContentFactoryImpl extends DiffContentFactory {
return new FileDocumentContentImpl(project, document, file);
}
@Override
@Nullable
public FileContent createFile(@Nullable Project project, @NotNull VirtualFile file) {
if (file.isDirectory()) return null;
return (FileContent)create(project, file);
}
@Override
@NotNull
public DiffContent createClipboardContent() {
@@ -18,8 +18,17 @@ package com.intellij.diff;
public abstract class DiffContextEx extends DiffContext {
/*
* Reopen current DiffRequest.
*
* perform the same procedure as on switching between DiffRequests or between DiffViewers.
* this can be used, if some change in request or settings was made, and we need to reopen DiffViewer to apply them.
*/
public abstract void reopenDiffRequest();
/*
* Drop cached DiffRequest version (if any) and reopen current DiffRequest.
*
* perform the same procedure as on opening DiffRequests for the first time.
* this can be used, if some change in request or settings was made, and we need to reload DiffRequest to apply them.
*/
public abstract void reloadDiffRequest();
}
@@ -19,15 +19,18 @@ import com.intellij.diff.chains.DiffRequestChain;
import com.intellij.diff.chains.SimpleDiffRequestChain;
import com.intellij.diff.impl.DiffRequestPanelImpl;
import com.intellij.diff.impl.DiffWindow;
import com.intellij.diff.merge.*;
import com.intellij.diff.requests.DiffRequest;
import com.intellij.diff.tools.binary.BinaryDiffTool;
import com.intellij.diff.tools.dir.DirDiffTool;
import com.intellij.diff.tools.external.ExternalDiffTool;
import com.intellij.diff.tools.external.ExternalMergeTool;
import com.intellij.diff.tools.fragmented.UnifiedDiffTool;
import com.intellij.diff.tools.simple.SimpleDiffTool;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import org.jetbrains.annotations.CalledInAwt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -93,4 +96,29 @@ public class DiffManagerImpl extends DiffManagerEx {
Collections.addAll(result, DiffTool.EP_NAME.getExtensions());
return result;
}
@NotNull
@Override
public List<MergeTool> getMergeTools() {
List<MergeTool> result = new ArrayList<MergeTool>();
Collections.addAll(result, MergeTool.EP_NAME.getExtensions());
result.add(TextMergeTool.INSTANCE);
result.add(BinaryMergeTool.INSTANCE);
return result;
}
@CalledInAwt
public void showMerge(@Nullable Project project, @NotNull MergeRequest request) {
if (ExternalMergeTool.isDefault()) {
ExternalMergeTool.show(project, request);
return;
}
showMergeBuiltin(project, request);
}
@CalledInAwt
public void showMergeBuiltin(@Nullable Project project, @NotNull MergeRequest request) {
new MergeWindow(project, request).show();
}
}
@@ -16,18 +16,40 @@
package com.intellij.diff;
import com.intellij.diff.contents.DiffContent;
import com.intellij.diff.contents.DocumentContent;
import com.intellij.diff.contents.FileAwareDocumentContent;
import com.intellij.diff.contents.FileContent;
import com.intellij.diff.merge.MergeRequest;
import com.intellij.diff.merge.MergeResult;
import com.intellij.diff.merge.TextMergeRequest;
import com.intellij.diff.requests.BinaryMergeRequestImpl;
import com.intellij.diff.requests.ContentDiffRequest;
import com.intellij.diff.requests.SimpleDiffRequest;
import com.intellij.diff.requests.TextMergeRequestImpl;
import com.intellij.diff.util.DiffUtil;
import com.intellij.openapi.diff.DiffBundle;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Consumer;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class DiffRequestFactoryImpl extends DiffRequestFactory {
private DiffContentFactory myContentFactory = DiffContentFactory.getInstance();
private final DiffContentFactory myContentFactory = DiffContentFactory.getInstance();
//
// Diff
//
@Override
@NotNull
@@ -57,6 +79,10 @@ public class DiffRequestFactoryImpl extends DiffRequestFactory {
return new SimpleDiffRequest(title, content1, content2, title1, title2);
}
//
// Titles
//
@Override
@NotNull
public String getContentTitle(@NotNull VirtualFile file) {
@@ -147,4 +173,195 @@ public class DiffRequestFactoryImpl extends DiffRequestFactory {
}
}
}
//
// Merge
//
@NotNull
public MergeRequest createMergeRequest(@Nullable Project project,
@Nullable FileType fileType,
@NotNull Document outputDocument,
@NotNull List<String> textContents,
@Nullable String title,
@NotNull List<String> titles,
@Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException {
if (textContents.size() != 3) throw new IllegalArgumentException();
if (titles.size() != 3) throw new IllegalArgumentException();
if (!DiffUtil.canMakeWritable(outputDocument)) throw new InvalidDiffRequestException("Output is read only");
DocumentContent outputContent = myContentFactory.create(project, outputDocument, fileType);
CharSequence originalContent = outputDocument.getImmutableCharSequence();
List<DocumentContent> contents = new ArrayList<DocumentContent>(3);
for (String text : textContents) {
contents.add(myContentFactory.create(text, fileType));
}
return new TextMergeRequestImpl(project, outputContent, originalContent, contents, title, titles, applyCallback);
}
@NotNull
@Override
public MergeRequest createMergeRequest(@Nullable Project project,
@NotNull VirtualFile output,
@NotNull List<byte[]> byteContents,
@Nullable String title,
@NotNull List<String> contentTitles,
@Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException {
if (byteContents.size() != 3) throw new IllegalArgumentException();
if (contentTitles.size() != 3) throw new IllegalArgumentException();
try {
return createTextMergeRequest(project, output, byteContents, title, contentTitles, applyCallback);
}
catch (InvalidDiffRequestException e) {
return createBinaryMergeRequest(project, output, byteContents, title, contentTitles, applyCallback);
}
}
@NotNull
@Override
public TextMergeRequest createTextMergeRequest(@Nullable Project project,
@NotNull VirtualFile output,
@NotNull List<byte[]> byteContents,
@Nullable String title,
@NotNull List<String> contentTitles,
@Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException {
if (byteContents.size() != 3) throw new IllegalArgumentException();
if (contentTitles.size() != 3) throw new IllegalArgumentException();
final Document outputDocument = FileDocumentManager.getInstance().getDocument(output);
if (outputDocument == null) throw new InvalidDiffRequestException("Can't get output document: " + output.getPresentableUrl());
if (!DiffUtil.canMakeWritable(outputDocument)) throw new InvalidDiffRequestException("Output is read only: " + output.getPresentableUrl());
DocumentContent outputContent = myContentFactory.create(project, outputDocument);
CharSequence originalContent = outputDocument.getImmutableCharSequence();
List<DocumentContent> contents = new ArrayList<DocumentContent>(3);
for (byte[] bytes : byteContents) {
contents.add(FileAwareDocumentContent.create(project, bytes, output));
}
return new TextMergeRequestImpl(project, outputContent, originalContent, contents, title, contentTitles, applyCallback);
}
@NotNull
@Override
public MergeRequest createBinaryMergeRequest(@Nullable Project project,
@NotNull VirtualFile output,
@NotNull List<byte[]> byteContents,
@Nullable String title,
@NotNull List<String> contentTitles,
@Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException {
if (byteContents.size() != 3) throw new IllegalArgumentException();
if (contentTitles.size() != 3) throw new IllegalArgumentException();
try {
FileContent outputContent = myContentFactory.createFile(project, output);
if (outputContent == null) throw new InvalidDiffRequestException("Can't create output content: " + output);
byte[] originalContent = output.contentsToByteArray();
List<DiffContent> contents = new ArrayList<DiffContent>(3);
for (byte[] bytes : byteContents) {
contents.add(myContentFactory.createFromBytes(project, output, bytes));
}
return new BinaryMergeRequestImpl(outputContent, originalContent, contents, byteContents, title, contentTitles, applyCallback);
}
catch (IOException e) {
throw new InvalidDiffRequestException("Can't read from file", e);
}
}
@NotNull
@Override
public MergeRequest createMergeRequestFromFiles(@Nullable Project project,
@NotNull VirtualFile output,
@NotNull List<VirtualFile> fileContents,
@Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException {
String title = "Merge " + output.getPresentableUrl();
List<String> titles = ContainerUtil.list("Your Version", "Base Version", "Their Version");
return createMergeRequestFromFiles(project, output, fileContents, title, titles, applyCallback);
}
@NotNull
@Override
public MergeRequest createMergeRequestFromFiles(@Nullable Project project,
@NotNull VirtualFile output,
@NotNull List<VirtualFile> fileContents,
@Nullable String title,
@NotNull List<String> contentTitles,
@Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException {
if (fileContents.size() != 3) throw new IllegalArgumentException();
if (contentTitles.size() != 3) throw new IllegalArgumentException();
try {
return createTextMergeRequestFromFiles(project, output, fileContents, title, contentTitles, applyCallback);
}
catch (InvalidDiffRequestException e) {
return createBinaryMergeRequestFromFiles(project, output, fileContents, title, contentTitles, applyCallback);
}
}
@NotNull
@Override
public TextMergeRequest createTextMergeRequestFromFiles(@Nullable Project project,
@NotNull VirtualFile output,
@NotNull List<VirtualFile> fileContents,
@Nullable String title,
@NotNull List<String> contentTitles,
@Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException {
if (fileContents.size() != 3) throw new IllegalArgumentException();
if (contentTitles.size() != 3) throw new IllegalArgumentException();
final Document outputDocument = FileDocumentManager.getInstance().getDocument(output);
if (outputDocument == null) throw new InvalidDiffRequestException("Can't get output document: " + output.getPresentableUrl());
if (!DiffUtil.canMakeWritable(outputDocument)) throw new InvalidDiffRequestException("Output is read only: " + output.getPresentableUrl());
DocumentContent outputContent = myContentFactory.create(project, outputDocument);
CharSequence originalContent = outputDocument.getImmutableCharSequence();
List<DocumentContent> contents = new ArrayList<DocumentContent>(3);
for (VirtualFile file : fileContents) {
DocumentContent document = myContentFactory.createDocument(project, file);
if (document == null) throw new InvalidDiffRequestException("Can't get text content: " + file.getPresentableUrl());
contents.add(document);
}
return new TextMergeRequestImpl(project, outputContent, originalContent, contents, title, contentTitles, applyCallback);
}
@NotNull
public MergeRequest createBinaryMergeRequestFromFiles(@Nullable Project project,
@NotNull VirtualFile output,
@NotNull List<VirtualFile> fileContents,
@Nullable String title,
@NotNull List<String> contentTitles,
@Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException {
if (fileContents.size() != 3) throw new IllegalArgumentException();
if (contentTitles.size() != 3) throw new IllegalArgumentException();
try {
FileContent outputContent = myContentFactory.createFile(project, output);
if (outputContent == null) throw new InvalidDiffRequestException("Can't create output content: " + output.getPresentableUrl());
byte[] originalContent = output.contentsToByteArray();
List<DiffContent> contents = new ArrayList<DiffContent>(3);
List<byte[]> byteContents = new ArrayList<byte[]>(3);
for (VirtualFile file : fileContents) {
FileContent content = myContentFactory.createFile(project, file);
if (content == null) throw new InvalidDiffRequestException("Can't create content: " + file.getPresentableUrl());
contents.add(content);
byteContents.add(file.contentsToByteArray()); // TODO: we can read contents from file when needed
}
return new BinaryMergeRequestImpl(outputContent, originalContent, contents, byteContents, title, contentTitles, applyCallback);
}
catch (IOException e) {
throw new InvalidDiffRequestException("Can't read from file", e);
}
}
}
@@ -19,6 +19,7 @@ import com.intellij.diff.tools.util.base.TextDiffSettingsHolder;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.EditorGutterComponentEx;
import com.intellij.openapi.editor.impl.EditorImpl;
import com.intellij.openapi.project.DumbAware;
import org.jetbrains.annotations.NotNull;
@@ -39,6 +40,10 @@ public class SetEditorSettingsAction extends ActionGroup implements DumbAware {
myTextSettings = settings;
myEditors = editors;
for (Editor editor : myEditors) {
((EditorGutterComponentEx)editor.getGutter()).setGutterPopupGroup(this);
}
myActions = new EditorSettingToggleAction[]{
new EditorSettingToggleAction("EditorToggleShowWhitespaces") {
@Override
@@ -19,6 +19,7 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ApplicationStarterEx;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
@@ -125,4 +126,9 @@ public abstract class ApplicationStarterBase extends ApplicationStarterEx {
public boolean canProcessExternalCommandLine() {
return true;
}
@Nullable
protected Project getProject() {
return null; // TODO: try to guess project
}
}
@@ -21,7 +21,6 @@ import com.intellij.diff.DiffRequestFactory;
import com.intellij.diff.requests.DiffRequest;
import com.intellij.openapi.application.ApplicationNamesInfo;
import com.intellij.openapi.diff.DiffBundle;
import com.intellij.openapi.project.DefaultProjectFactory;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
@@ -47,8 +46,6 @@ public class DiffApplication extends ApplicationStarterBase {
}
public void processCommand(@NotNull String[] args, @Nullable String currentDirectory) throws Exception {
// TODO: try to guess 'right' project ?
final String path1 = args[1];
final String path2 = args[2];
@@ -59,9 +56,10 @@ public class DiffApplication extends ApplicationStarterBase {
if (file2 == null) throw new Exception("Can't find file " + path2);
VfsUtil.markDirtyAndRefresh(false, false, false, file1, file2);
DiffRequest request = DiffRequestFactory.getInstance().createFromFiles(null, file1, file2);
Project project = DefaultProjectFactory.getInstance().getDefaultProject();
Project project = getProject();
DiffRequest request = DiffRequestFactory.getInstance().createFromFiles(project, file1, file2);
DiffManagerEx.getInstance().showDiffBuiltin(project, request, DiffDialogHints.MODAL);
}
}
@@ -0,0 +1,84 @@
/*
* Copyright 2000-2015 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.diff.applications;
import com.intellij.diff.DiffManagerEx;
import com.intellij.diff.DiffRequestFactory;
import com.intellij.diff.merge.MergeRequest;
import com.intellij.openapi.application.ApplicationNamesInfo;
import com.intellij.openapi.diff.DiffBundle;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@SuppressWarnings({"UseOfSystemOutOrSystemErr", "CallToPrintStackTrace"})
public class MergeApplication extends ApplicationStarterBase {
@Override
protected boolean checkArguments(@NotNull String[] args) {
return (args.length == 4 || args.length == 5) && "merge".equals(args[0]);
}
@Override
public String getCommandName() {
return "merge";
}
@NotNull
public String getUsageMessage() {
final String scriptName = ApplicationNamesInfo.getInstance().getScriptName();
return DiffBundle.message("merge.application.usage.parameters.and.description", scriptName);
}
public void processCommand(@NotNull String[] args, @Nullable String currentDirectory) throws Exception {
// TODO: try to guess 'right' project ?
final String path1 = args[1];
final String path2 = args[2];
final String path3 = args[3];
final String path4 = args.length == 5 ? args[4] : args[3];
final VirtualFile file1 = findFile(path1, currentDirectory);
final VirtualFile file2 = findFile(path2, currentDirectory);
final VirtualFile file3 = findFile(path3, currentDirectory);
final VirtualFile file4 = findFile(path4, currentDirectory);
if (file1 == null) throw new Exception("Can't find file " + path1);
if (file2 == null) throw new Exception("Can't find file " + path2);
if (file3 == null) throw new Exception("Can't find file " + path3);
if (file4 == null) throw new Exception("Can't find file " + path4);
file1.refresh(false, true);
file2.refresh(false, true);
file3.refresh(false, true);
file4.refresh(false, true);
Project project = getProject();
List<VirtualFile> contents = ContainerUtil.list(file1, file3, file2); // left, base, right
MergeRequest request = DiffRequestFactory.getInstance().createMergeRequestFromFiles(project, file4, contents, null);
DiffManagerEx.getInstance().showMergeBuiltin(project, request);
Document document = FileDocumentManager.getInstance().getCachedDocument(file4);
if (document != null) FileDocumentManager.getInstance().saveDocument(document);
}
}
@@ -33,7 +33,6 @@ import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.util.ProgressWindow;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.util.Consumer;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
@@ -64,8 +63,18 @@ public abstract class CacheDiffRequestChainProcessor extends DiffRequestProcesso
// Update
//
@Override
protected void reloadRequest() {
updateRequest(true, false, null);
}
@CalledInAwt
public void updateRequest(final boolean force, @Nullable final ScrollToPolicy scrollToChangePolicy) {
updateRequest(force, true, scrollToChangePolicy);
}
@CalledInAwt
public void updateRequest(final boolean force, boolean useCache, @Nullable final ScrollToPolicy scrollToChangePolicy) {
List<? extends DiffRequestProducer> requests = myRequestChain.getRequests();
int index = myRequestChain.getIndex();
if (index < 0 || index >= requests.size()) {
@@ -75,7 +84,7 @@ public abstract class CacheDiffRequestChainProcessor extends DiffRequestProcesso
final DiffRequestProducer producer = requests.get(index);
DiffRequest request = loadRequestFast(producer);
DiffRequest request = loadRequestFast(producer, useCache);
if (request != null) {
applyRequest(request, force, scrollToChangePolicy);
return;
@@ -107,7 +116,8 @@ public abstract class CacheDiffRequestChainProcessor extends DiffRequestProcesso
}
@Nullable
protected DiffRequest loadRequestFast(@NotNull DiffRequestProducer producer) {
protected DiffRequest loadRequestFast(@NotNull DiffRequestProducer producer, boolean useCache) {
if (!useCache) return null;
return myRequestCache.get(producer);
}
@@ -126,6 +136,7 @@ public abstract class CacheDiffRequestChainProcessor extends DiffRequestProcesso
return new ErrorDiffRequest(producer, e);
}
catch (Exception e) {
LOG.warn(e);
return new ErrorDiffRequest(producer, e);
}
}
@@ -32,10 +32,10 @@ import java.awt.*;
public class DiffRequestPanelImpl implements DiffRequestPanel {
@NotNull private final JPanel myPanel;
@NotNull private final MyCacheDiffRequestChainProcessor myProcessor;
@NotNull private final MyDiffRequestProcessor myProcessor;
public DiffRequestPanelImpl(@Nullable Project project, @Nullable Window window) {
myProcessor = new MyCacheDiffRequestChainProcessor(project, window);
myProcessor = new MyDiffRequestProcessor(project, window);
myProcessor.putContextUserData(DiffUserDataKeys.DO_NOT_CHANGE_WINDOW_TITLE, true);
myPanel = new JPanel(new BorderLayout()) {
@@ -81,12 +81,12 @@ public class DiffRequestPanelImpl implements DiffRequestPanel {
Disposer.dispose(myProcessor);
}
private static class MyCacheDiffRequestChainProcessor extends DiffRequestProcessor {
private static class MyDiffRequestProcessor extends DiffRequestProcessor {
@Nullable private final Window myWindow;
@NotNull private DiffRequest myRequest = NoDiffRequest.INSTANCE;
public MyCacheDiffRequestChainProcessor(@Nullable Project project, @Nullable Window window) {
public MyDiffRequestProcessor(@Nullable Project project, @Nullable Window window) {
super(project);
myWindow = window;
}
@@ -158,6 +158,11 @@ public abstract class DiffRequestProcessor implements Disposable {
// Update
//
@CalledInAwt
protected void reloadRequest() {
updateRequest(true);
}
@CalledInAwt
public void updateRequest() {
updateRequest(false);
@@ -445,13 +450,13 @@ public abstract class DiffRequestProcessor implements Disposable {
myToolbarPanel.setContent(toolbar.getComponent());
for (AnAction action : group.getChildren(null)) {
action.registerCustomShortcutSet(action.getShortcutSet(), myMainPanel);
DiffUtil.registerAction(action, myMainPanel);
}
}
protected void buildActionPopup(@Nullable List<AnAction> viewerActions) {
ShowActionGroupPopupAction action = new ShowActionGroupPopupAction();
action.registerCustomShortcutSet(action.getShortcutSet(), myMainPanel);
DiffUtil.registerAction(action, myMainPanel);
myPopupActionGroup = collectPopupActions(viewerActions);
}
@@ -902,10 +907,15 @@ public abstract class DiffRequestProcessor implements Disposable {
}
@Override
public void reloadDiffRequest() {
public void reopenDiffRequest() {
updateRequest(true);
}
@Override
public void reloadDiffRequest() {
reloadRequest();
}
@Nullable
@Override
public Project getProject() {
@@ -63,6 +63,8 @@ public abstract class DiffWindowBase {
myProcessor.requestFocus(); // TODO: not needed for modal dialogs. Make a flag in WindowWrapperBuilder ?
}
})
.setOkAction(myHints.getOkAction())
.setCancelAction(myHints.getCancelAction())
.build();
myWrapper.setImage(ImageLoader.loadFromResource("/diff/Diff.png"));
Disposer.register(myWrapper, myProcessor);
@@ -0,0 +1,179 @@
/*
* Copyright 2000-2015 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.diff.merge;
import com.intellij.diff.DiffContext;
import com.intellij.diff.FrameDiffTool;
import com.intellij.diff.contents.DiffContent;
import com.intellij.diff.requests.ContentDiffRequest;
import com.intellij.diff.requests.DiffRequest;
import com.intellij.diff.requests.SimpleDiffRequest;
import com.intellij.diff.tools.binary.ThreesideBinaryDiffViewer;
import com.intellij.diff.tools.holders.BinaryEditorHolder;
import com.intellij.openapi.diff.DiffBundle;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.BooleanGetter;
import com.intellij.openapi.util.Disposer;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.CalledInAwt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.util.List;
public class BinaryMergeTool implements MergeTool {
public static final BinaryMergeTool INSTANCE = new BinaryMergeTool();
@NotNull
@Override
public MergeViewer createComponent(@NotNull MergeContext context, @NotNull MergeRequest request) {
return new BinaryMergeViewer(context, (ThreesideMergeRequest)request);
}
@Override
public boolean canShow(@NotNull MergeContext context, @NotNull MergeRequest request) {
if (!(request instanceof ThreesideMergeRequest)) return false;
MergeUtil.ProxyDiffContext diffContext = new MergeUtil.ProxyDiffContext(context);
for (DiffContent diffContent : ((ThreesideMergeRequest)request).getContents()) {
if (!BinaryEditorHolder.BinaryEditorHolderFactory.INSTANCE.canShowContent(diffContent, diffContext)) return false;
}
return true;
}
public static class BinaryMergeViewer implements MergeViewer {
@NotNull private final MergeContext myMergeContext;
@NotNull private final ThreesideMergeRequest myMergeRequest;
@NotNull private final DiffContext myDiffContext;
@NotNull private final ContentDiffRequest myDiffRequest;
@NotNull private final MyThreesideViewer myViewer;
public BinaryMergeViewer(@NotNull MergeContext context, @NotNull ThreesideMergeRequest request) {
myMergeContext = context;
myMergeRequest = request;
myDiffContext = new MergeUtil.ProxyDiffContext(myMergeContext);
myDiffRequest = new SimpleDiffRequest(myMergeRequest.getTitle(),
getDiffContents(myMergeRequest),
getDiffContentTitles(myMergeRequest));
myViewer = new MyThreesideViewer(myDiffContext, myDiffRequest);
}
@NotNull
private static List<DiffContent> getDiffContents(@NotNull ThreesideMergeRequest mergeRequest) {
return ContainerUtil.newArrayList(mergeRequest.getContents());
}
@NotNull
private static List<String> getDiffContentTitles(@NotNull ThreesideMergeRequest mergeRequest) {
return MergeUtil.notNullizeContentTitles(mergeRequest.getContentTitles());
}
//
// Impl
//
@NotNull
@Override
public JComponent getComponent() {
return myViewer.getComponent();
}
@Nullable
@Override
public JComponent getPreferredFocusedComponent() {
return myViewer.getPreferredFocusedComponent();
}
@NotNull
@Override
public ToolbarComponents init() {
ToolbarComponents components = new ToolbarComponents();
FrameDiffTool.ToolbarComponents init = myViewer.init();
components.statusPanel = init.statusPanel;
components.toolbarActions = init.toolbarActions;
components.closeHandler = new BooleanGetter() {
@Override
public boolean get() {
return Messages.showYesNoDialog(getComponent().getRootPane(),
DiffBundle.message("merge.dialog.exit.without.applying.changes.confirmation.message"),
DiffBundle.message("cancel.visual.merge.dialog.title"), Messages.getQuestionIcon()) == Messages.YES;
}
};
return components;
}
@Nullable
@Override
public Action getResolveAction(@NotNull final MergeResult result) {
if (result == MergeResult.RESOLVED) return null;
String caption = MergeUtil.getResolveActionTitle(result, myMergeRequest, myMergeContext);
return new AbstractAction(caption) {
@Override
public void actionPerformed(ActionEvent e) {
if (result == MergeResult.CANCEL) {
if (Messages.showYesNoDialog(getComponent().getRootPane(),
DiffBundle.message("merge.dialog.exit.without.applying.changes.confirmation.message"),
DiffBundle.message("cancel.visual.merge.dialog.title"), Messages.getQuestionIcon()) != Messages.YES) {
return;
}
}
myMergeContext.finishMerge(result);
}
};
}
@Override
public void dispose() {
Disposer.dispose(myViewer);
}
//
// Getters
//
@NotNull
public MyThreesideViewer getViewer() {
return myViewer;
}
//
// Viewer
//
private static class MyThreesideViewer extends ThreesideBinaryDiffViewer {
public MyThreesideViewer(@NotNull DiffContext context, @NotNull DiffRequest request) {
super(context, request);
}
@Override
@CalledInAwt
public void rediff(boolean trySync) {
}
}
}
}
@@ -0,0 +1,95 @@
/*
* Copyright 2000-2015 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.diff.merge;
import com.intellij.diff.util.DiffUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
public class ErrorMergeTool implements MergeTool {
public static final ErrorMergeTool INSTANCE = new ErrorMergeTool();
@NotNull
@Override
public MergeViewer createComponent(@NotNull MergeContext context, @NotNull MergeRequest request) {
return new MyViewer(context, request);
}
@Override
public boolean canShow(@NotNull MergeContext context, @NotNull MergeRequest request) {
return true;
}
private static class MyViewer implements MergeViewer {
@NotNull private final MergeContext myMergeContext;
@NotNull private final MergeRequest myMergeRequest;
@NotNull private final JPanel myPanel;
public MyViewer(@NotNull MergeContext context, @NotNull MergeRequest request) {
myMergeContext = context;
myMergeRequest = request;
myPanel = new JPanel(new BorderLayout());
myPanel.add(createComponent(), BorderLayout.CENTER);
}
@NotNull
private JComponent createComponent() {
return DiffUtil.createMessagePanel("Can't show diff");
}
@NotNull
@Override
public JComponent getComponent() {
return myPanel;
}
@Nullable
@Override
public JComponent getPreferredFocusedComponent() {
return null;
}
@NotNull
@Override
public ToolbarComponents init() {
return new ToolbarComponents();
}
@Nullable
@Override
public Action getResolveAction(@NotNull final MergeResult result) {
if (result == MergeResult.RESOLVED) return null;
String caption = MergeUtil.getResolveActionTitle(result, myMergeRequest, myMergeContext);
return new AbstractAction(caption) {
@Override
public void actionPerformed(ActionEvent e) {
myMergeContext.finishMerge(result);
}
};
}
@Override
public void dispose() {
}
}
}
@@ -0,0 +1,440 @@
/*
* Copyright 2000-2015 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.diff.merge;
import com.intellij.diff.DiffManagerEx;
import com.intellij.diff.actions.impl.NextDifferenceAction;
import com.intellij.diff.actions.impl.PrevDifferenceAction;
import com.intellij.diff.tools.util.DiffDataKeys;
import com.intellij.diff.tools.util.PrevNextDifferenceIterable;
import com.intellij.diff.util.DiffPlaces;
import com.intellij.diff.util.DiffUserDataKeys;
import com.intellij.diff.util.DiffUserDataKeysEx;
import com.intellij.diff.util.DiffUtil;
import com.intellij.ide.DataManager;
import com.intellij.ide.impl.DataManagerImpl;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.BooleanGetter;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy;
import com.intellij.ui.components.panels.Wrapper;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.CalledInAwt;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.util.List;
// TODO: support merge request chains
// idea - to keep in memory all viewers that were modified (so binary conflict is not the case and OOM shouldn't be too often)
// suspend() / resume() methods for viewers? To not interfere with MergeRequest lifecycle: single request -> single viewer -> single applyResult()
public abstract class MergeRequestProcessor implements Disposable {
private static final Logger LOG = Logger.getInstance(MergeRequestProcessor.class);
private boolean myDisposed;
@Nullable private final Project myProject;
@NotNull private final MergeContext myContext;
@NotNull private final List<MergeTool> myAvailableTools;
@NotNull private final JPanel myPanel;
@NotNull private final MyPanel myMainPanel;
@NotNull private final Wrapper myContentPanel;
@NotNull private final Wrapper myToolbarPanel;
@NotNull private final Wrapper myToolbarStatusPanel;
@NotNull private final MergeRequest myRequest;
@NotNull private final MergeTool.MergeViewer myViewer;
@Nullable private BooleanGetter myCloseHandler;
private boolean myConflictResolved = false;
public MergeRequestProcessor(@Nullable Project project, @NotNull MergeRequest request) {
myProject = project;
myRequest = request;
myContext = new MyDiffContext();
myContext.putUserData(DiffUserDataKeysEx.PLACE, DiffPlaces.MERGE);
myAvailableTools = DiffManagerEx.getInstance().getMergeTools();
myPanel = new JPanel(new BorderLayout());
myMainPanel = new MyPanel();
myContentPanel = new Wrapper();
myToolbarPanel = new Wrapper();
myToolbarPanel.setFocusable(true);
myToolbarStatusPanel = new Wrapper();
myPanel.add(myMainPanel, BorderLayout.CENTER);
JPanel topPanel = new JPanel(new BorderLayout());
topPanel.add(myToolbarPanel, BorderLayout.CENTER);
topPanel.add(myToolbarStatusPanel, BorderLayout.EAST);
myMainPanel.add(topPanel, BorderLayout.NORTH);
myMainPanel.add(myContentPanel, BorderLayout.CENTER);
myMainPanel.setFocusTraversalPolicyProvider(true);
myMainPanel.setFocusTraversalPolicy(new MyFocusTraversalPolicy());
MergeTool.MergeViewer viewer;
try {
MergeTool tool = getFittedTool();
viewer = tool.createComponent(myContext, myRequest);
}
catch (Throwable e) {
LOG.error(e);
viewer = ErrorMergeTool.INSTANCE.createComponent(myContext, myRequest);
}
myViewer = viewer;
}
//
// Update
//
public void init() {
myContentPanel.setContent(myViewer.getComponent());
setTitle(myRequest.getTitle());
MergeTool.ToolbarComponents toolbarComponents = myViewer.init();
buildToolbar(toolbarComponents.toolbarActions);
myToolbarStatusPanel.setContent(toolbarComponents.statusPanel);
myCloseHandler = toolbarComponents.closeHandler;
}
@NotNull
public BottomActions getBottomActions() {
BottomActions actions = new BottomActions();
actions.applyLeft = myViewer.getResolveAction(MergeResult.LEFT);
actions.applyRight = myViewer.getResolveAction(MergeResult.RIGHT);
actions.resolveAction = myViewer.getResolveAction(MergeResult.RESOLVED);
actions.cancelAction = myViewer.getResolveAction(MergeResult.CANCEL);
return actions;
}
@NotNull
protected DefaultActionGroup collectToolbarActions(@Nullable List<AnAction> viewerActions) {
DefaultActionGroup group = new DefaultActionGroup();
List<AnAction> navigationActions = ContainerUtil.<AnAction>list(new MyPrevDifferenceAction(),
new MyNextDifferenceAction());
DiffUtil.addActionBlock(group, navigationActions);
DiffUtil.addActionBlock(group, viewerActions);
List<AnAction> requestContextActions = myRequest.getUserData(DiffUserDataKeys.CONTEXT_ACTIONS);
DiffUtil.addActionBlock(group, requestContextActions);
List<AnAction> contextActions = myContext.getUserData(DiffUserDataKeys.CONTEXT_ACTIONS);
DiffUtil.addActionBlock(group, contextActions);
return group;
}
protected void buildToolbar(@Nullable List<AnAction> viewerActions) {
ActionGroup group = collectToolbarActions(viewerActions);
ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.DIFF_TOOLBAR, group, true);
DataManager.registerDataProvider(toolbar.getComponent(), myMainPanel);
toolbar.setTargetComponent(toolbar.getComponent());
myToolbarPanel.setContent(toolbar.getComponent());
for (AnAction action : group.getChildren(null)) {
DiffUtil.registerAction(action, myMainPanel);
}
}
@NotNull
private MergeTool getFittedTool() {
for (MergeTool tool : myAvailableTools) {
try {
if (tool.canShow(myContext, myRequest)) return tool;
}
catch (Throwable e) {
LOG.error(e);
}
}
return ErrorMergeTool.INSTANCE;
}
private void setTitle(@Nullable String title) {
if (title == null) title = "Merge";
setWindowTitle(title);
}
@Override
public void dispose() {
if (myDisposed) return;
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
if (myDisposed) return;
myDisposed = true;
onDispose();
Disposer.dispose(myViewer);
myToolbarStatusPanel.setContent(null);
myToolbarPanel.setContent(null);
myContentPanel.setContent(null);
}
});
}
@CalledInAwt
private void applyRequestResult(@NotNull MergeResult result) {
if (myConflictResolved) return;
myConflictResolved = true;
try {
myRequest.applyResult(result);
}
catch (Exception e) {
LOG.error(e);
}
}
//
// Abstract
//
@CalledInAwt
protected void onDispose() {
applyRequestResult(MergeResult.CANCEL);
}
protected void setWindowTitle(@NotNull String title) {
}
public abstract void closeDialog();
@Nullable
public <T> T getContextUserData(@NotNull Key<T> key) {
return myContext.getUserData(key);
}
public <T> void putContextUserData(@NotNull Key<T> key, @Nullable T value) {
myContext.putUserData(key, value);
}
//
// Getters
//
@NotNull
public JComponent getComponent() {
return myPanel;
}
@Nullable
public JComponent getPreferredFocusedComponent() {
JComponent component = myViewer.getPreferredFocusedComponent();
return component != null ? component : myToolbarPanel.getTargetComponent();
}
@Nullable
public Project getProject() {
return myProject;
}
@NotNull
public MergeContext getContext() {
return myContext;
}
@CalledInAwt
public boolean checkCloseAction() {
return myConflictResolved || myCloseHandler == null || myCloseHandler.get();
}
@Nullable
public String getHelpId() {
return PlatformDataKeys.HELP_ID.getData(myMainPanel);
}
//
// Misc
//
public boolean isFocused() {
return DiffUtil.isFocusedComponent(myProject, myPanel);
}
public void requestFocus() {
DiffUtil.requestFocus(myProject, getPreferredFocusedComponent());
}
protected void requestFocusInternal() {
JComponent component = getPreferredFocusedComponent();
if (component != null) component.requestFocus();
}
//
// Navigation
//
private static class MyNextDifferenceAction extends NextDifferenceAction {
@Override
public void update(@NotNull AnActionEvent e) {
if (!ActionPlaces.DIFF_TOOLBAR.equals(e.getPlace())) {
e.getPresentation().setEnabled(true);
return;
}
PrevNextDifferenceIterable iterable = DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.getData(e.getDataContext());
if (iterable != null && iterable.canGoNext()) {
e.getPresentation().setEnabled(true);
return;
}
e.getPresentation().setEnabled(false);
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
PrevNextDifferenceIterable iterable = DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.getData(e.getDataContext());
if (iterable != null && iterable.canGoNext()) {
iterable.goNext();
}
}
}
private static class MyPrevDifferenceAction extends PrevDifferenceAction {
@Override
public void update(@NotNull AnActionEvent e) {
if (!ActionPlaces.DIFF_TOOLBAR.equals(e.getPlace())) {
e.getPresentation().setEnabled(true);
return;
}
PrevNextDifferenceIterable iterable = DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.getData(e.getDataContext());
if (iterable != null && iterable.canGoPrev()) {
e.getPresentation().setEnabled(true);
return;
}
e.getPresentation().setEnabled(false);
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
PrevNextDifferenceIterable iterable = DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.getData(e.getDataContext());
if (iterable != null && iterable.canGoPrev()) {
iterable.goPrev();
}
}
}
//
// Helpers
//
private class MyPanel extends JPanel implements DataProvider {
public MyPanel() {
super(new BorderLayout());
}
@Nullable
@Override
public Object getData(@NonNls String dataId) {
Object data;
DataProvider contentProvider = DataManagerImpl.getDataProviderEx(myContentPanel.getTargetComponent());
if (contentProvider != null) {
data = contentProvider.getData(dataId);
if (data != null) return data;
}
if (CommonDataKeys.PROJECT.is(dataId)) {
return myProject;
}
else if (PlatformDataKeys.HELP_ID.is(dataId)) {
if (myRequest.getUserData(DiffUserDataKeys.HELP_ID) != null) {
return myRequest.getUserData(DiffUserDataKeys.HELP_ID);
}
else {
return "procedures.vcWithIDEA.commonVcsOps.integrateDiffs.resolveConflict";
}
}
DataProvider requestProvider = myRequest.getUserData(DiffUserDataKeys.DATA_PROVIDER);
if (requestProvider != null) {
data = requestProvider.getData(dataId);
if (data != null) return data;
}
DataProvider contextProvider = myContext.getUserData(DiffUserDataKeys.DATA_PROVIDER);
if (contextProvider != null) {
data = contextProvider.getData(dataId);
if (data != null) return data;
}
return null;
}
}
private class MyFocusTraversalPolicy extends IdeFocusTraversalPolicy {
@Override
public final Component getDefaultComponentImpl(final Container focusCycleRoot) {
JComponent component = MergeRequestProcessor.this.getPreferredFocusedComponent();
if (component == null) return null;
return IdeFocusTraversalPolicy.getPreferredFocusedComponent(component, this);
}
}
private class MyDiffContext extends MergeContext {
@Nullable
@Override
public Project getProject() {
return MergeRequestProcessor.this.getProject();
}
@Override
public boolean isFocused() {
return MergeRequestProcessor.this.isFocused();
}
@Override
public void requestFocus() {
MergeRequestProcessor.this.requestFocusInternal();
}
@Override
public void finishMerge(@NotNull MergeResult result) {
applyRequestResult(result);
MergeRequestProcessor.this.closeDialog();
}
}
public static class BottomActions {
@Nullable public Action applyLeft;
@Nullable public Action applyRight;
@Nullable public Action resolveAction;
@Nullable public Action cancelAction;
}
}
@@ -0,0 +1,93 @@
/*
* Copyright 2000-2015 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.diff.merge;
import com.intellij.diff.DiffContext;
import com.intellij.diff.util.ThreeSide;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class MergeUtil {
@NotNull
public static String getResolveActionTitle(@NotNull MergeResult result, @NotNull MergeRequest request, @NotNull MergeContext context) {
switch (result) {
case CANCEL:
return "Abort";
case LEFT:
return "Accept Left";
case RIGHT:
return "Accept Right";
case RESOLVED:
return "Apply";
default:
throw new IllegalArgumentException(result.toString());
}
}
@NotNull
public static List<String> notNullizeContentTitles(@NotNull List<String> mergeContentTitles) {
String left = StringUtil.notNullize(ThreeSide.LEFT.select(mergeContentTitles), "Your Version");
String base = StringUtil.notNullize(ThreeSide.BASE.select(mergeContentTitles), "Base Version");
String right = StringUtil.notNullize(ThreeSide.RIGHT.select(mergeContentTitles), "Server Version");
return ContainerUtil.list(left, base, right);
}
public static class ProxyDiffContext extends DiffContext {
@NotNull private final MergeContext myMergeContext;
public ProxyDiffContext(@NotNull MergeContext mergeContext) {
myMergeContext = mergeContext;
}
@Nullable
@Override
public Project getProject() {
return myMergeContext.getProject();
}
@Override
public boolean isWindowFocused() {
return true;
}
@Override
public boolean isFocused() {
return myMergeContext.isFocused();
}
@Override
public void requestFocus() {
myMergeContext.requestFocus();
}
@Nullable
@Override
public <T> T getUserData(@NotNull Key<T> key) {
return myMergeContext.getUserData(key);
}
@Override
public <T> void putUserData(@NotNull Key<T> key, @Nullable T value) {
myMergeContext.putUserData(key, value);
}
}
}
@@ -0,0 +1,165 @@
/*
* Copyright 2000-2015 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.diff.merge;
import com.intellij.diff.util.DiffUserDataKeys;
import com.intellij.diff.util.DiffUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.List;
public class MergeWindow {
@Nullable private final Project myProject;
@NotNull private final MergeRequest myMergeRequest;
private MyDialog myWrapper;
public MergeWindow(@Nullable Project project, @NotNull MergeRequest mergeRequest) {
myProject = project;
myMergeRequest = mergeRequest;
}
protected void init() {
MergeRequestProcessor processor = new MergeRequestProcessor(myProject, myMergeRequest) {
@Override
public void closeDialog() {
myWrapper.doCancelAction();
}
@Override
protected void setWindowTitle(@NotNull String title) {
myWrapper.setTitle(title);
}
};
myWrapper = new MyDialog(processor);
myWrapper.init();
}
public void show() {
init();
myWrapper.show();
}
// TODO: use WindowWrapper
private static class MyDialog extends DialogWrapper {
@NotNull private final MergeRequestProcessor myProcessor;
@NotNull private final MergeRequestProcessor.BottomActions myBottomActions;
public MyDialog(@NotNull MergeRequestProcessor processor) {
super(processor.getProject(), true);
myProcessor = processor;
myBottomActions = myProcessor.getBottomActions();
}
@Override
public void init() {
super.init();
Disposer.register(getDisposable(), myProcessor);
getWindow().addWindowListener(new WindowAdapter() {
@Override
public void windowOpened(WindowEvent e) {
myProcessor.init();
}
});
}
@Nullable
@Override
protected JComponent createCenterPanel() {
return new MyPanel(myProcessor.getComponent());
}
@Nullable
@Override
public JComponent getPreferredFocusedComponent() {
return myProcessor.getPreferredFocusedComponent();
}
@Nullable
@Override
protected String getDimensionServiceKey() {
return StringUtil.notNullize(myProcessor.getContextUserData(DiffUserDataKeys.DIALOG_GROUP_KEY), "MergeDialog");
}
@NotNull
@Override
protected Action[] createActions() {
List<Action> actions = ContainerUtil.skipNulls(ContainerUtil.list(myBottomActions.resolveAction, myBottomActions.cancelAction));
if (myBottomActions.resolveAction != null) {
myBottomActions.resolveAction.putValue(DialogWrapper.DEFAULT_ACTION, true);
}
return actions.toArray(new Action[actions.size()]);
}
@NotNull
@Override
protected Action[] createLeftSideActions() {
List<Action> actions = ContainerUtil.skipNulls(ContainerUtil.list(myBottomActions.applyLeft, myBottomActions.applyRight));
return actions.toArray(new Action[actions.size()]);
}
@NotNull
@Override
protected Action getOKAction() {
if (myBottomActions.resolveAction != null) return myBottomActions.resolveAction;
return super.getOKAction();
}
@NotNull
@Override
protected Action getCancelAction() {
if (myBottomActions.cancelAction != null) return myBottomActions.cancelAction;
return super.getCancelAction();
}
@Nullable
@Override
protected String getHelpId() {
return myProcessor.getHelpId();
}
@Override
public void doCancelAction() {
if (!myProcessor.checkCloseAction()) return;
super.doCancelAction();
}
}
private static class MyPanel extends JPanel {
public MyPanel(@NotNull JComponent content) {
super(new BorderLayout());
add(content, BorderLayout.CENTER);
}
@Override
public Dimension getPreferredSize() {
Dimension windowSize = DiffUtil.getDefaultDiffWindowSize();
Dimension size = super.getPreferredSize();
return new Dimension(Math.max(windowSize.width, size.width), Math.max(windowSize.height, size.height));
}
}
}
@@ -0,0 +1,455 @@
/*
* Copyright 2000-2015 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.diff.merge;
import com.intellij.diff.comparison.ComparisonPolicy;
import com.intellij.diff.fragments.MergeLineFragment;
import com.intellij.diff.tools.simple.ThreesideDiffChangeBase;
import com.intellij.diff.util.*;
import com.intellij.diff.util.DiffUtil.UpdatedLineRange;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.diff.DiffBundle;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.markup.*;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.CalledInAwt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class TextMergeChange extends ThreesideDiffChangeBase {
@NotNull private final TextMergeTool.TextMergeViewer myMergeViewer;
@NotNull private final TextMergeTool.TextMergeViewer.MyThreesideViewer myViewer;
@NotNull private final List<RangeHighlighter> myHighlighters = new ArrayList<RangeHighlighter>();
@NotNull private final List<MyGutterOperation> myOperations = new ArrayList<MyGutterOperation>();
private final int[] myStartLines = new int[3];
private final int[] myEndLines = new int[3];
private final boolean[] myResolved = new boolean[2];
private boolean myOnesideAppliedConflict;
@CalledInAwt
public TextMergeChange(@NotNull MergeLineFragment fragment, @NotNull TextMergeTool.TextMergeViewer viewer) {
super(fragment, viewer.getViewer().getEditors(), ComparisonPolicy.DEFAULT);
myMergeViewer = viewer;
myViewer = viewer.getViewer();
for (ThreeSide side : ThreeSide.values()) {
myStartLines[side.getIndex()] = fragment.getStartLine(side);
myEndLines[side.getIndex()] = fragment.getEndLine(side);
}
installHighlighter();
}
void installHighlighter() {
assert myHighlighters.isEmpty();
createHighlighter(ThreeSide.BASE);
if (getType().isLeftChange()) createHighlighter(ThreeSide.LEFT);
if (getType().isRightChange()) createHighlighter(ThreeSide.RIGHT);
doInstallActionHighlighters();
}
@CalledInAwt
void destroyHighlighter() {
for (RangeHighlighter highlighter : myHighlighters) {
highlighter.dispose();
}
myHighlighters.clear();
for (MyGutterOperation operation : myOperations) {
operation.dispose();
}
myOperations.clear();
}
@CalledInAwt
void doReinstallHighlighter() {
destroyHighlighter();
installHighlighter();
myViewer.repaintDividers();
}
private void createHighlighter(@NotNull ThreeSide side) {
Editor editor = side.select(myViewer.getEditors());
Document document = editor.getDocument();
TextDiffType type = getDiffType();
boolean resolved = isResolved(side);
int startLine = getStartLine(side);
int endLine = getEndLine(side);
int start;
int end;
if (startLine == endLine) {
start = end = startLine < DiffUtil.getLineCount(document) ? document.getLineStartOffset(startLine) : document.getTextLength();
}
else {
start = document.getLineStartOffset(startLine);
end = document.getLineEndOffset(endLine - 1);
if (end < document.getTextLength()) end++;
}
myHighlighters.addAll(DiffDrawUtil.createHighlighter(editor, start, end, type, false, HighlighterTargetArea.EXACT_RANGE, resolved));
if (startLine == endLine) {
if (startLine != 0) {
myHighlighters.addAll(DiffDrawUtil.createLineMarker(editor, endLine - 1, type, SeparatorPlacement.BOTTOM, true, resolved));
}
}
else {
myHighlighters.addAll(DiffDrawUtil.createLineMarker(editor, startLine, type, SeparatorPlacement.TOP, false, resolved));
myHighlighters.addAll(DiffDrawUtil.createLineMarker(editor, endLine - 1, type, SeparatorPlacement.BOTTOM, false, resolved));
}
}
//
// Getters
//
@CalledInAwt
void setResolved(@NotNull Side side, boolean value) {
myResolved[side.getIndex()] = value;
}
public boolean isResolved() {
return myResolved[0] && myResolved[1];
}
public boolean isResolved(@NotNull Side side) {
return side.select(myResolved);
}
public boolean isOnesideAppliedConflict() {
return myOnesideAppliedConflict;
}
public void markOnesideAppliedConflict() {
myOnesideAppliedConflict = true;
}
public boolean isResolved(@NotNull ThreeSide side) {
switch (side) {
case LEFT:
return isResolved(Side.LEFT);
case BASE:
return isResolved();
case RIGHT:
return isResolved(Side.RIGHT);
default:
throw new IllegalArgumentException(side.toString());
}
}
public int getStartLine(@NotNull ThreeSide side) {
return side.select(myStartLines);
}
public int getEndLine(@NotNull ThreeSide side) {
return side.select(myEndLines);
}
public void setStartLine(@NotNull ThreeSide side, int value) {
myStartLines[side.getIndex()] = value;
}
public void setEndLine(@NotNull ThreeSide side, int value) {
myEndLines[side.getIndex()] = value;
}
//
// Shift
//
@Nullable
State processBaseChange(int oldLine1, int oldLine2, int shift) {
int line1 = getStartLine(ThreeSide.BASE);
int line2 = getEndLine(ThreeSide.BASE);
UpdatedLineRange newRange = DiffUtil.updateRangeOnModification(line1, line2, oldLine1, oldLine2, shift);
boolean rangeAffected = newRange.damaged ||
(oldLine2 >= line1 && oldLine1 <= line2); // RangeMarker can be updated in a different way
State oldState = rangeAffected ? storeState() : null;
if (newRange.startLine == newRange.endLine && getDiffType() == TextDiffType.DELETED && !isResolved()) {
if (oldState == null) oldState = storeState();
myViewer.markChangeResolved(this);
}
setStartLine(ThreeSide.BASE, newRange.startLine);
setEndLine(ThreeSide.BASE, newRange.endLine);
return oldState;
}
//
// Gutter actions
//
private void doInstallActionHighlighters() {
ContainerUtil.addIfNotNull(myOperations, createOperation(ThreeSide.LEFT, OperationType.APPLY));
ContainerUtil.addIfNotNull(myOperations, createOperation(ThreeSide.LEFT, OperationType.IGNORE));
ContainerUtil.addIfNotNull(myOperations, createOperation(ThreeSide.RIGHT, OperationType.APPLY));
ContainerUtil.addIfNotNull(myOperations, createOperation(ThreeSide.RIGHT, OperationType.IGNORE));
}
@Nullable
private MyGutterOperation createOperation(@NotNull ThreeSide side, @NotNull OperationType type) {
if (isResolved(side)) return null;
EditorEx editor = myViewer.getEditor(side);
Document document = editor.getDocument();
int line = getStartLine(side);
int offset = line == DiffUtil.getLineCount(document) ? document.getTextLength() : document.getLineStartOffset(line);
RangeHighlighter highlighter = editor.getMarkupModel().addRangeHighlighter(offset, offset,
HighlighterLayer.ADDITIONAL_SYNTAX,
null,
HighlighterTargetArea.LINES_IN_RANGE);
return new MyGutterOperation(side, highlighter, type);
}
public void updateGutterActions(boolean force) {
for (MyGutterOperation operation : myOperations) {
operation.update(force);
}
}
private class MyGutterOperation {
@NotNull private final ThreeSide mySide;
@NotNull private final RangeHighlighter myHighlighter;
@NotNull private final OperationType myType;
private boolean myCtrlPressed;
private boolean myShiftPressed;
private MyGutterOperation(@NotNull ThreeSide side, @NotNull RangeHighlighter highlighter, @NotNull OperationType type) {
mySide = side;
myHighlighter = highlighter;
myType = type;
update(true);
}
public void dispose() {
myHighlighter.dispose();
}
public void update(boolean force) {
if (!force && !areModifiersChanged()) {
return;
}
if (myHighlighter.isValid()) myHighlighter.setGutterIconRenderer(createRenderer());
}
private boolean areModifiersChanged() {
return myCtrlPressed != myViewer.getModifierProvider().isCtrlPressed() ||
myShiftPressed != myViewer.getModifierProvider().isShiftPressed();
}
@Nullable
public GutterIconRenderer createRenderer() {
if (mySide == ThreeSide.BASE) return null;
Side versionSide = mySide.select(Side.LEFT, null, Side.RIGHT);
assert versionSide != null;
myCtrlPressed = myViewer.getModifierProvider().isCtrlPressed();
myShiftPressed = myViewer.getModifierProvider().isShiftPressed();
if (!isChange(versionSide)) return null;
switch (myType) {
case APPLY:
return createApplyRenderer(versionSide, myCtrlPressed);
case IGNORE:
return createIgnoreRenderer(versionSide, myCtrlPressed);
default:
throw new IllegalArgumentException(myType.name());
}
}
}
@Nullable
private GutterIconRenderer createApplyRenderer(@NotNull final Side side, final boolean modifier) {
if (isResolved(side)) return null;
Icon icon = isOnesideAppliedConflict() ? AllIcons.Diff.ArrowLeftDown : AllIcons.Diff.Arrow;
return createIconRenderer(DiffBundle.message("merge.dialog.apply.change.action.name"), icon, new Runnable() {
@Override
public void run() {
myViewer.executeMergeCommand("Apply change", Collections.singletonList(TextMergeChange.this), new Runnable() {
@Override
public void run() {
myViewer.replaceChange(TextMergeChange.this, side, modifier);
}
});
}
});
}
@Nullable
private GutterIconRenderer createIgnoreRenderer(@NotNull final Side side, final boolean modifier) {
if (isResolved(side)) return null;
return createIconRenderer(DiffBundle.message("merge.dialog.ignore.change.action.name"), AllIcons.Diff.Remove, new Runnable() {
@Override
public void run() {
myViewer.executeMergeCommand(null, Collections.singletonList(TextMergeChange.this), new Runnable() {
@Override
public void run() {
myViewer.ignoreChange(TextMergeChange.this, side, modifier);
}
});
}
});
}
@Nullable
private GutterIconRenderer createIconRenderer(@NotNull final String tooltipText,
@NotNull final Icon icon,
@NotNull final Runnable perform) {
return new GutterIconRenderer() {
@NotNull
@Override
public Icon getIcon() {
return icon;
}
public boolean isNavigateAction() {
return true;
}
@Nullable
@Override
public AnAction getClickAction() {
return new DumbAwareAction() {
@Override
public void actionPerformed(AnActionEvent e) {
perform.run();
}
};
}
@Override
public boolean equals(Object obj) {
return obj == this;
}
@Override
public int hashCode() {
return System.identityHashCode(this);
}
@Nullable
@Override
public String getTooltipText() {
return tooltipText;
}
@Override
public boolean isDumbAware() {
return true;
}
};
}
private enum OperationType {
APPLY, IGNORE
}
//
// State
//
@NotNull
State storeState() {
return new State(
myStartLines[0],
myStartLines[1],
myStartLines[2],
myEndLines[0],
myEndLines[1],
myEndLines[2],
myResolved[0],
myResolved[1],
myOnesideAppliedConflict);
}
void restoreState(@NotNull State state) {
myStartLines[0] = state.myStartLine1;
myStartLines[1] = state.myStartLine2;
myStartLines[2] = state.myStartLine3;
myEndLines[0] = state.myEndLine1;
myEndLines[1] = state.myEndLine2;
myEndLines[2] = state.myEndLine3;
myResolved[0] = state.myResolved1;
myResolved[1] = state.myResolved2;
myOnesideAppliedConflict = state.myOnesideAppliedConflict;
}
public static class State {
private final int myStartLine1;
private final int myStartLine2;
private final int myStartLine3;
private final int myEndLine1;
private final int myEndLine2;
private final int myEndLine3;
private final boolean myResolved1;
private final boolean myResolved2;
private final boolean myOnesideAppliedConflict;
public State(int startLine1,
int startLine2,
int startLine3,
int endLine1,
int endLine2,
int endLine3,
boolean resolved1,
boolean resolved2,
boolean onesideAppliedConflict) {
myStartLine1 = startLine1;
myStartLine2 = startLine2;
myStartLine3 = startLine3;
myEndLine1 = endLine1;
myEndLine2 = endLine2;
myEndLine3 = endLine3;
myResolved1 = resolved1;
myResolved2 = resolved2;
myOnesideAppliedConflict = onesideAppliedConflict;
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,132 @@
/*
* Copyright 2000-2015 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.diff.requests;
import com.intellij.CommonBundle;
import com.intellij.diff.contents.DiffContent;
import com.intellij.diff.contents.FileContent;
import com.intellij.diff.merge.ThreesideMergeRequest;
import com.intellij.diff.merge.MergeResult;
import com.intellij.diff.util.ThreeSide;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.util.Consumer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.List;
public class BinaryMergeRequestImpl extends ThreesideMergeRequest {
private static final Logger LOG = Logger.getInstance(BinaryMergeRequestImpl.class);
@NotNull private final FileContent myFile;
@NotNull private final List<DiffContent> myContents;
@NotNull private final List<byte[]> myByteContents;
@NotNull private final byte[] myOriginalContent;
@Nullable private final String myTitle;
@NotNull private final List<String> myTitles;
@Nullable private final Consumer<MergeResult> myApplyCallback;
public BinaryMergeRequestImpl(@NotNull FileContent file,
@NotNull byte[] originalContent,
@NotNull List<DiffContent> contents,
@NotNull List<byte[]> byteContents,
@Nullable String title,
@NotNull List<String> contentTitles,
@Nullable Consumer<MergeResult> applyCallback) {
assert byteContents.size() == 3;
assert contents.size() == 3;
assert contentTitles.size() == 3;
myFile = file;
myOriginalContent = originalContent;
myByteContents = byteContents;
myContents = contents;
myTitle = title;
myTitles = contentTitles;
myApplyCallback = applyCallback;
}
@NotNull
@Override
public FileContent getOutputContent() {
return myFile;
}
@NotNull
@Override
public List<DiffContent> getContents() {
return myContents;
}
@Nullable
@Override
public String getTitle() {
return myTitle;
}
@NotNull
@Override
public List<String> getContentTitles() {
return myTitles;
}
@Override
public void applyResult(@NotNull MergeResult result) {
final byte[] applyContent;
switch (result) {
case CANCEL:
applyContent = myOriginalContent;
break;
case LEFT:
applyContent = ThreeSide.LEFT.select(myByteContents);
break;
case RIGHT:
applyContent = ThreeSide.RIGHT.select(myByteContents);
break;
case RESOLVED:
applyContent = null;
break;
default:
throw new IllegalArgumentException(result.toString());
}
if (applyContent != null) {
new WriteCommandAction.Simple(null) {
@Override
protected void run() throws Throwable {
try {
myFile.getFile().setBinaryContent(applyContent);
}
catch (IOException e) {
LOG.error(e);
Messages.showErrorDialog((Project)null, "Can't apply result", CommonBundle.getErrorTitle());
}
}
}.execute();
}
if (myApplyCallback != null) myApplyCallback.consume(result);
}
}
@@ -0,0 +1,121 @@
/*
* Copyright 2000-2015 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.diff.requests;
import com.intellij.diff.contents.DocumentContent;
import com.intellij.diff.merge.MergeResult;
import com.intellij.diff.merge.TextMergeRequest;
import com.intellij.diff.util.ThreeSide;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.Consumer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class TextMergeRequestImpl extends TextMergeRequest {
@Nullable private final Project myProject;
@NotNull private final DocumentContent myOutput;
@NotNull private final List<DocumentContent> myContents;
@NotNull private final CharSequence myOriginalContent;
@Nullable private final String myTitle;
@NotNull private final List<String> myTitles;
@Nullable private final Consumer<MergeResult> myApplyCallback;
public TextMergeRequestImpl(@Nullable Project project,
@NotNull DocumentContent output,
@NotNull CharSequence originalContent,
@NotNull List<DocumentContent> contents,
@Nullable String title,
@NotNull List<String> contentTitles,
@Nullable Consumer<MergeResult> applyCallback) {
assert contents.size() == 3;
assert contentTitles.size() == 3;
myProject = project;
myOutput = output;
myOriginalContent = originalContent;
myContents = contents;
myTitles = contentTitles;
myTitle = title;
myApplyCallback = applyCallback;
}
@NotNull
@Override
public DocumentContent getOutputContent() {
return myOutput;
}
@NotNull
@Override
public List<DocumentContent> getContents() {
return myContents;
}
@Nullable
@Override
public String getTitle() {
return myTitle;
}
@NotNull
@Override
public List<String> getContentTitles() {
return myTitles;
}
@Override
public void applyResult(@NotNull MergeResult result) {
final CharSequence applyContent;
switch (result) {
case CANCEL:
applyContent = myOriginalContent;
break;
case LEFT:
CharSequence leftContent = ThreeSide.LEFT.select(getContents()).getDocument().getImmutableCharSequence();
applyContent = StringUtil.convertLineSeparators(leftContent.toString());
break;
case RIGHT:
CharSequence rightContent = ThreeSide.RIGHT.select(getContents()).getDocument().getImmutableCharSequence();
applyContent = StringUtil.convertLineSeparators(rightContent.toString());
break;
case RESOLVED:
applyContent = null;
break;
default:
throw new IllegalArgumentException(result.toString());
}
if (applyContent != null) {
new WriteCommandAction.Simple(myProject) {
@Override
protected void run() throws Throwable {
myOutput.getDocument().setText(applyContent);
}
}.execute();
}
if (myApplyCallback != null) myApplyCallback.consume(result);
}
}
@@ -0,0 +1,87 @@
/*
* Copyright 2000-2015 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.diff.requests;
import com.intellij.diff.DiffContext;
import com.intellij.diff.DiffContextEx;
import com.intellij.diff.util.DiffUtil;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.fileTypes.UnknownFileType;
import com.intellij.openapi.fileTypes.ex.FileTypeChooser;
import com.intellij.openapi.vcs.changes.issueLinks.LinkMouseListenerBase;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.SimpleColoredComponent;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
public class UnknownFileTypeDiffRequest extends ComponentDiffRequest {
@Nullable private final String myFileName;
@Nullable private final String myTitle;
public UnknownFileTypeDiffRequest(@NotNull VirtualFile file, @Nullable String title) {
this(file.getName(), title);
}
public UnknownFileTypeDiffRequest(@NotNull String fileName, @Nullable String title) {
boolean knownFileType = FileTypeManager.getInstance().getFileTypeByFileName(fileName) != UnknownFileType.INSTANCE;
myFileName = knownFileType ? null : fileName;
myTitle = title;
}
public UnknownFileTypeDiffRequest(@Nullable String title) {
myFileName = null;
myTitle = title;
}
@NotNull
@Override
public JComponent getComponent(@NotNull final DiffContext context) {
final SimpleColoredComponent label = new SimpleColoredComponent();
label.append("Can't show diff for unknown file type. ",
new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, UIUtil.getInactiveTextColor()));
if (myFileName != null) {
label.append("Associate", SimpleTextAttributes.LINK_ATTRIBUTES, new Runnable() {
@Override
public void run() {
FileType type = FileTypeChooser.associateFileType(myFileName);
if (type != null) onSuccess(context);
}
});
LinkMouseListenerBase.installSingleTagOn(label);
}
return DiffUtil.createMessagePanel(label);
}
@Nullable
public String getFileName() {
return myFileName;
}
@Nullable
@Override
public String getTitle() {
return myTitle;
}
protected void onSuccess(@NotNull DiffContext context) {
if (context instanceof DiffContextEx) ((DiffContextEx)context).reloadDiffRequest();
}
}
@@ -84,7 +84,7 @@
</component>
</children>
</grid>
<grid id="d5bc7" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="d5bc7" layout-manager="GridLayoutManager" row-count="1" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="3" column="0" row-span="1" col-span="4" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
@@ -103,9 +103,18 @@
</component>
<hspacer id="3caba">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
<grid row="0" column="2" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
</hspacer>
<component id="5eb41" class="javax.swing.JCheckBox" binding="myRespectExitCodeCheckbox">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<margin top="4" left="2" bottom="0" right="2"/>
<text value="Trust process exit code"/>
</properties>
</component>
</children>
</grid>
<component id="7e9c5" class="javax.swing.JLabel">
@@ -50,6 +50,7 @@ public class ExternalDiffSettingsPanel {
private TextFieldWithBrowseButton myMergePath;
private JTextField myMergeParameters;
private JLabel myDescriptionLabel;
private JCheckBox myRespectExitCodeCheckbox;
public ExternalDiffSettingsPanel() {
myDescriptionLabel.setText(DESCRIPTION_TEXT);
@@ -87,6 +88,7 @@ public class ExternalDiffSettingsPanel {
if (mySettings.isMergeEnabled() != myMergeEnabled.isSelected()) return true;
if (!mySettings.getMergeExePath().equals(myMergePath.getText())) return true;
if (!mySettings.getMergeParameters().equals(myMergeParameters.getText())) return true;
if (mySettings.isMergeTrustExitCode() != myRespectExitCodeCheckbox.isSelected()) return true;
return false;
}
@@ -100,6 +102,7 @@ public class ExternalDiffSettingsPanel {
mySettings.setMergeEnabled(myMergeEnabled.isSelected());
mySettings.setMergeExePath(myMergePath.getText());
mySettings.setMergeParameters(myMergeParameters.getText());
mySettings.setMergeTrustExitCode(myRespectExitCodeCheckbox.isSelected());
}
public void reset() {
@@ -111,6 +114,7 @@ public class ExternalDiffSettingsPanel {
myMergePath.setText(mySettings.getMergeExePath());
myMergeEnabled.setSelected(mySettings.isMergeEnabled());
myMergeParameters.setText(mySettings.getMergeParameters());
myRespectExitCodeCheckbox.setSelected(mySettings.isMergeTrustExitCode());
updateEnabledEffect();
}
@@ -122,6 +126,7 @@ public class ExternalDiffSettingsPanel {
UIUtil.setEnabled(myMergePath, myMergeEnabled.isSelected(), true);
UIUtil.setEnabled(myMergeParameters, myMergeEnabled.isSelected(), true);
UIUtil.setEnabled(myRespectExitCodeCheckbox, myMergeEnabled.isSelected(), true);
}
private void createUIComponents() {
@@ -20,19 +20,15 @@ import com.intellij.diff.DiffContextEx;
import com.intellij.diff.FrameDiffTool;
import com.intellij.diff.contents.DiffContent;
import com.intellij.diff.contents.FileContent;
import com.intellij.diff.requests.ContentDiffRequest;
import com.intellij.diff.requests.DiffRequest;
import com.intellij.diff.requests.MessageDiffRequest;
import com.intellij.diff.requests.*;
import com.intellij.diff.util.DiffUtil;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.fileTypes.FileTypes;
import com.intellij.openapi.fileTypes.ex.FileTypeChooser;
import com.intellij.openapi.vcs.changes.issueLinks.LinkMouseListenerBase;
import com.intellij.openapi.fileTypes.UnknownFileType;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.SimpleColoredComponent;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -79,26 +75,17 @@ public class ErrorDiffTool implements FrameDiffTool {
String message = ((MessageDiffRequest)request).getMessage();
return DiffUtil.createMessagePanel(message);
}
if (request instanceof ComponentDiffRequest) {
return ((ComponentDiffRequest)request).getComponent(myContext);
}
if (request instanceof ContentDiffRequest) {
List<DiffContent> contents = ((ContentDiffRequest)request).getContents();
for (DiffContent content : contents) {
for (final DiffContent content : contents) {
if (content instanceof FileContent && FileTypes.UNKNOWN.equals(content.getContentType())) {
final VirtualFile file = ((FileContent)content).getFile();
final SimpleColoredComponent label = new SimpleColoredComponent();
label.append("Can't show diff for unknown file type. ",
new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, UIUtil.getInactiveTextColor()));
label.append("Associate", SimpleTextAttributes.LINK_ATTRIBUTES, new Runnable() {
@Override
public void run() {
FileType type = FileTypeChooser.associateFileType(file.getName());
if (type != null && myContext instanceof DiffContextEx) {
((DiffContextEx)myContext).reloadDiffRequest();
}
}
});
LinkMouseListenerBase.installSingleTagOn(label);
return DiffUtil.createMessagePanel(label);
UnknownFileTypeDiffRequest unknownFileTypeRequest = new UnknownFileTypeDiffRequest(file, myRequest.getTitle());
return unknownFileTypeRequest.getComponent(myContext);
}
}
}
@@ -121,6 +108,21 @@ public class ErrorDiffTool implements FrameDiffTool {
@NotNull
@Override
public ToolbarComponents init() {
if (myRequest instanceof UnknownFileTypeDiffRequest) {
String fileName = ((UnknownFileTypeDiffRequest)myRequest).getFileName();
if (fileName != null && FileTypeManager.getInstance().getFileTypeByFileName(fileName) != UnknownFileType.INSTANCE) {
// FileType was assigned elsewhere (ex: by other UnknownFileTypeDiffRequest). We should reload request.
if (myContext instanceof DiffContextEx) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
((DiffContextEx)myContext).reloadDiffRequest();
}
}, ModalityState.current());
}
}
}
return new ToolbarComponents();
}
@@ -91,6 +91,7 @@ public class ExternalDiffSettings implements PersistentStateComponent<ExternalDi
@Nullable public Boolean MERGE_ENABLED = null;
@Nullable public String MERGE_EXE_PATH = null;
@Nullable public String MERGE_PARAMETERS = null;
public boolean MERGE_TRUST_EXIT_CODE = false;
}
public boolean isDiffEnabled() {
@@ -158,4 +159,12 @@ public class ExternalDiffSettings implements PersistentStateComponent<ExternalDi
myState.MERGE_PARAMETERS = path;
setProperty(DiffManagerImpl.MERGE_TOOL_PARAMETERS, path);
}
public boolean isMergeTrustExitCode() {
return myState.MERGE_TRUST_EXIT_CODE;
}
public void setMergeTrustExitCode(boolean value) {
myState.MERGE_TRUST_EXIT_CODE = value;
}
}
@@ -16,18 +16,28 @@
package com.intellij.diff.tools.external;
import com.intellij.diff.contents.*;
import com.intellij.diff.merge.MergeResult;
import com.intellij.diff.merge.ThreesideMergeRequest;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.CommandLineTokenizer;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.LineSeparator;
import com.intellij.util.PathUtil;
import com.intellij.util.TimeoutUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -36,6 +46,8 @@ import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class ExternalDiffToolUtil {
public static boolean canCreateFile(@NotNull DiffContent content) {
@@ -59,31 +71,12 @@ public class ExternalDiffToolUtil {
return file.getPath();
}
byte[] bytes = file.contentsToByteArray();
return createFile(bytes, getFileName(title, windowTitle, content.getContentType())).getPath();
String tempFileName = getFileName(title, windowTitle, content.getContentType());
return createTempFile(file, tempFileName).getPath();
}
else if (content instanceof DocumentContent) {
final DocumentContent documentContent = (DocumentContent)content;
FileDocumentManager.getInstance().saveDocument(documentContent.getDocument());
LineSeparator separator = documentContent.getLineSeparator();
if (separator == null) separator = LineSeparator.getSystemLineSeparator();
Charset charset = documentContent.getCharset();
if (charset == null) charset = Charset.defaultCharset();
String contentData = ApplicationManager.getApplication().runReadAction(new Computable<String>() {
@Override
public String compute() {
return documentContent.getDocument().getText();
}
});
if (separator != LineSeparator.LF) {
contentData = StringUtil.convertLineSeparators(contentData, separator.getSeparatorString());
}
byte[] bytes = contentData.getBytes(charset);
return createFile(bytes, getFileName(title, windowTitle, content.getContentType())).getPath();
String tempFileName = getFileName(title, windowTitle, content.getContentType());
return createTempFile(((DocumentContent)content), tempFileName).getPath();
}
else if (content instanceof DirectoryContent) {
VirtualFile file = ((DirectoryContent)content).getFile();
@@ -97,6 +90,56 @@ public class ExternalDiffToolUtil {
throw new IllegalArgumentException(content.toString());
}
@NotNull
private static File createTempFile(@NotNull final DocumentContent content, @NotNull String tempFileName) throws IOException {
FileDocumentManager.getInstance().saveDocument(content.getDocument());
LineSeparator separator = content.getLineSeparator();
if (separator == null) separator = LineSeparator.getSystemLineSeparator();
Charset charset = content.getCharset();
if (charset == null) charset = Charset.defaultCharset();
String contentData = ApplicationManager.getApplication().runReadAction(new Computable<String>() {
@Override
public String compute() {
return content.getDocument().getText();
}
});
if (separator != LineSeparator.LF) {
contentData = StringUtil.convertLineSeparators(contentData, separator.getSeparatorString());
}
byte[] bytes = contentData.getBytes(charset);
return createFile(bytes, tempFileName);
}
@NotNull
private static File createTempFile(@NotNull VirtualFile file, @NotNull String tempFileName) throws IOException {
byte[] bytes = file.contentsToByteArray();
return createFile(bytes, tempFileName);
}
@NotNull
public static OutputFile createOutputFile(@NotNull DiffContent content, @Nullable String windowTitle) throws IOException {
if (content instanceof FileContent) {
FileContent fileContent = (FileContent)content;
if (fileContent.getFile().isInLocalFileSystem()) {
return new LocalOutputFile(fileContent.getFile());
}
String tempFileName = getFileName(null, windowTitle, content.getContentType());
File tempFile = createTempFile(fileContent.getFile(), tempFileName);
return new NonLocalOutputFile(fileContent.getFile(), tempFile);
}
else if (content instanceof DocumentContent) {
String tempFileName = getFileName(null, windowTitle, content.getContentType());
File tempFile = createTempFile(((DocumentContent)content), tempFileName);
return new DocumentOutputFile(((DocumentContent)content).getDocument(), ((DocumentContent)content).getCharset(), tempFile);
}
throw new IllegalArgumentException(content.toString());
}
@NotNull
public static String getFileName(@Nullable String title, @Nullable String windowTitle, @Nullable FileType fileType) {
String prefix = "";
@@ -120,7 +163,7 @@ public class ExternalDiffToolUtil {
}
public static void execute(@NotNull ExternalDiffSettings settings,
@NotNull List<DiffContent> contents,
@NotNull List<? extends DiffContent> contents,
@NotNull List<String> titles,
@Nullable String windowTitle)
throws IOException, ExecutionException {
@@ -162,4 +205,194 @@ public class ExternalDiffToolUtil {
commandLine.addParameters(args);
commandLine.createProcess();
}
public static void executeMerge(@Nullable Project project,
@NotNull ExternalDiffSettings settings,
@NotNull ThreesideMergeRequest request)
throws IOException, ExecutionException {
boolean success = false;
try {
DiffContent outputContent = request.getOutputContent();
List<? extends DiffContent> contents = request.getContents();
List<String> titles = request.getContentTitles();
String windowTitle = request.getTitle();
assert contents.size() == 3;
assert titles.size() == contents.size();
List<String> files = new ArrayList<String>();
for (int i = 0; i < contents.size(); i++) {
files.add(createFile(contents.get(i), titles.get(i), windowTitle));
}
OutputFile outputFile = createOutputFile(outputContent, windowTitle);
CommandLineTokenizer parameterTokenizer = new CommandLineTokenizer(settings.getMergeParameters(), true);
List<String> args = new ArrayList<String>();
while (parameterTokenizer.hasMoreTokens()) {
String arg = parameterTokenizer.nextToken();
if ("%1".equals(arg)) {
args.add(files.get(0));
}
else if ("%2".equals(arg)) {
args.add(files.get(2));
}
else if ("%3".equals(arg)) {
args.add(files.get(1));
}
else if ("%4".equals(arg)) {
args.add(outputFile.getPath());
}
else {
args.add(arg);
}
}
GeneralCommandLine commandLine = new GeneralCommandLine();
commandLine.setExePath(settings.getMergeExePath());
commandLine.addParameters(args);
final Process process = commandLine.createProcess();
if (settings.isMergeTrustExitCode()) {
final Ref<Boolean> resultRef = new Ref<Boolean>();
ProgressManager.getInstance().run(new Task.Modal(project, "Waiting for external tool", true) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
final Semaphore semaphore = new Semaphore(0);
final Thread waiter = new Thread("external process waiter") {
@Override
public void run() {
try {
resultRef.set(process.waitFor() == 0);
}
catch (InterruptedException ignore) {
}
finally {
semaphore.release();
}
}
};
waiter.start();
try {
while (true) {
indicator.checkCanceled();
if (semaphore.tryAcquire(200, TimeUnit.MILLISECONDS)) break;
}
}
catch (InterruptedException ignore) {
}
finally {
waiter.interrupt();
}
}
});
success = resultRef.get() == Boolean.TRUE;
}
else {
ProgressManager.getInstance().run(new Task.Modal(project, "Launching external tool", false) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
indicator.setIndeterminate(true);
TimeoutUtil.sleep(1000);
}
});
success = Messages.showYesNoDialog(project,
"Press \"Mark as Resolved\" when you finish resolving conflicts in the external tool",
"Merge In External Tool", "Mark as Resolved", "Revert", null) == Messages.YES;
}
if (success) outputFile.finish();
}
finally {
request.applyResult(success ? MergeResult.RESOLVED : MergeResult.CANCEL);
}
}
//
// Helpers
//
private interface OutputFile {
@NotNull
String getPath();
void finish() throws IOException;
}
private static class LocalOutputFile implements OutputFile {
@NotNull private final VirtualFile myFile;
public LocalOutputFile(@NotNull VirtualFile file) {
myFile = file;
}
@NotNull
@Override
public String getPath() {
return myFile.getPath();
}
@Override
public void finish() {
myFile.refresh(false, false);
}
}
private static class NonLocalOutputFile implements OutputFile {
@NotNull private final VirtualFile myFile;
@NotNull private final File myLocalFile;
public NonLocalOutputFile(@NotNull VirtualFile file, @NotNull File localFile) {
myFile = file;
myLocalFile = localFile;
}
@NotNull
@Override
public String getPath() {
return myLocalFile.getPath();
}
@Override
public void finish() throws IOException {
myFile.setBinaryContent(FileUtil.loadFileBytes(myLocalFile));
}
}
private static class DocumentOutputFile implements OutputFile {
@NotNull private final Document myDocument;
@NotNull private final File myLocalFile;
@NotNull private final Charset myCharset;
public DocumentOutputFile(@NotNull Document document, @Nullable Charset charset, @NotNull File localFile) {
myDocument = document;
myLocalFile = localFile;
// TODO: potentially dangerous operation - we're using default charset
myCharset = charset != null ? charset : Charset.defaultCharset();
}
@NotNull
@Override
public String getPath() {
return myLocalFile.getPath();
}
@Override
public void finish() throws IOException {
final String content = StringUtil.convertLineSeparators(FileUtil.loadFile(myLocalFile, myCharset));
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
myDocument.setText(content);
}
});
}
}
}
@@ -0,0 +1,91 @@
/*
* Copyright 2000-2015 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.diff.tools.external;
import com.intellij.diff.DiffManagerEx;
import com.intellij.diff.contents.DiffContent;
import com.intellij.diff.contents.DocumentContent;
import com.intellij.diff.contents.FileContent;
import com.intellij.diff.merge.MergeRequest;
import com.intellij.diff.merge.ThreesideMergeRequest;
import com.intellij.execution.ExecutionException;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.List;
public class ExternalMergeTool {
public static final Logger LOG = Logger.getInstance(ExternalMergeTool.class);
public static boolean isDefault() {
return ExternalDiffSettings.getInstance().isMergeEnabled();
}
public static boolean isEnabled() {
return ExternalDiffSettings.getInstance().isMergeEnabled();
}
public static void show(@Nullable final Project project,
@NotNull final MergeRequest request) {
try {
if (canShow(request)) {
showRequest(project, request);
}
else {
DiffManagerEx.getInstance().showMergeBuiltin(project, request);
}
}
catch (ProcessCanceledException ignore) {
}
catch (Throwable e) {
LOG.error(e);
Messages.showErrorDialog(project, e.getMessage(), "Can't Show Merge In External Tool");
}
}
public static void showRequest(@Nullable Project project, @NotNull MergeRequest request)
throws ExecutionException, IOException {
ExternalDiffSettings settings = ExternalDiffSettings.getInstance();
ExternalDiffToolUtil.executeMerge(project, settings, (ThreesideMergeRequest)request);
}
public static boolean canShow(@NotNull MergeRequest request) {
if (request instanceof ThreesideMergeRequest) {
DiffContent outputContent = ((ThreesideMergeRequest)request).getOutputContent();
if (!canProcessOutputContent(outputContent)) return false;
List<? extends DiffContent> contents = ((ThreesideMergeRequest)request).getContents();
if (contents.size() != 3) return false;
for (DiffContent content : contents) {
if (!ExternalDiffToolUtil.canCreateFile(content)) return false;
}
return true;
}
return false;
}
private static boolean canProcessOutputContent(@NotNull DiffContent content) {
if (content instanceof DocumentContent) return true;
if (content instanceof FileContent && ((FileContent)content).getFile().isInLocalFileSystem()) return true;
return false;
}
}
@@ -257,13 +257,17 @@ public class UnifiedDiffChange {
return new DumbAwareAction() {
@Override
public void actionPerformed(AnActionEvent e) {
if (myViewer.isStateIsOutOfDate()) return;
if (!myViewer.isEditable(sourceSide.other(), true)) return;
final Project project = e.getProject();
final Document document = myViewer.getDocument(sourceSide.other());
DiffUtil.executeWriteCommand(document, project, "Replace change", new Runnable() {
@Override
public void run() {
myViewer.applyChange(UnifiedDiffChange.this, sourceSide);
myViewer.replaceChange(UnifiedDiffChange.this, sourceSide);
myViewer.scheduleRediff();
}
});
// applyChange() will schedule rediff, but we want to try to do it in sync
@@ -30,10 +30,9 @@ import com.intellij.diff.tools.util.base.*;
import com.intellij.diff.tools.util.side.TwosideTextDiffViewer;
import com.intellij.diff.util.*;
import com.intellij.diff.util.DiffUserDataKeysEx.ScrollToPolicy;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.Separator;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.undo.UndoManager;
import com.intellij.openapi.diagnostic.Logger;
@@ -50,6 +49,7 @@ import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Pair;
@@ -60,10 +60,7 @@ import gnu.trove.TIntFunction;
import org.jetbrains.annotations.*;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.*;
import static com.intellij.diff.util.DiffUtil.getLineCount;
@@ -124,6 +121,11 @@ public class UnifiedDiffViewer extends ListenerDiffViewerBase {
new MyOpenInEditorWithMouseAction().register(getEditors());
TextDiffViewerUtil.checkDifferentDocuments(myRequest);
DiffUtil.registerAction(new ReplaceSelectedChangesAction(Side.LEFT, true), myPanel);
DiffUtil.registerAction(new AppendSelectedChangesAction(Side.LEFT, true), myPanel);
DiffUtil.registerAction(new ReplaceSelectedChangesAction(Side.RIGHT, true), myPanel);
DiffUtil.registerAction(new AppendSelectedChangesAction(Side.RIGHT, true), myPanel);
}
@Override
@@ -133,6 +135,7 @@ public class UnifiedDiffViewer extends ListenerDiffViewerBase {
installEditorListeners();
installTypingSupport();
myPanel.setLoadingContent(); // We need loading panel only for initial rediff()
myPanel.setPersistentNotifications(DiffUtil.getCustomNotifications(myContext, myRequest));
}
@Override
@@ -208,7 +211,19 @@ public class UnifiedDiffViewer extends ListenerDiffViewerBase {
@NotNull
protected List<AnAction> createEditorPopupActions() {
return TextDiffViewerUtil.createEditorPopupActions();
List<AnAction> group = new ArrayList<AnAction>();
group.add(new ReplaceSelectedChangesAction(Side.LEFT, false));
group.add(new AppendSelectedChangesAction(Side.LEFT, false));
group.add(new ReplaceSelectedChangesAction(Side.RIGHT, false));
group.add(new AppendSelectedChangesAction(Side.RIGHT, false));
group.add(new RevertSelectedChangesAction(Side.LEFT));
group.add(new RevertSelectedChangesAction(Side.RIGHT));
group.add(Separator.getInstance());
group.addAll(TextDiffViewerUtil.createEditorPopupActions());
return group;
}
@CalledInAwt
@@ -621,26 +636,163 @@ public class UnifiedDiffViewer extends ListenerDiffViewerBase {
scheduleRediff();
}
@CalledWithWriteLock
public void applyChange(@NotNull UnifiedDiffChange change, @NotNull Side sourceSide) {
if (myStateIsOutOfDate || myChangedBlockData == null) return;
//
// Modification operations
//
Side affectedSide = sourceSide.other();
if (!isEditable(affectedSide, true)) return;
private abstract class ApplySelectedChangesActionBase extends AnAction implements DumbAware {
@NotNull protected final Side myModifiedSide;
private final boolean myShortcut;
public ApplySelectedChangesActionBase(@NotNull Side modifiedSide, boolean shortcut) {
myModifiedSide = modifiedSide;
myShortcut = shortcut;
}
@Override
public void update(@NotNull AnActionEvent e) {
if (myShortcut) {
// consume shortcut even if there are nothing to do - avoid calling some other action
e.getPresentation().setEnabledAndVisible(true);
return;
}
Editor editor = e.getData(CommonDataKeys.EDITOR);
if (editor != getEditor()) {
e.getPresentation().setEnabledAndVisible(false);
return;
}
if (!isEditable(myModifiedSide, true) || isStateIsOutOfDate()) {
e.getPresentation().setEnabledAndVisible(false);
return;
}
e.getPresentation().setVisible(true);
e.getPresentation().setEnabled(isSomeChangeSelected());
}
@Override
public void actionPerformed(@NotNull final AnActionEvent e) {
final List<UnifiedDiffChange> selectedChanges = getSelectedChanges();
if (selectedChanges.isEmpty()) return;
if (!isEditable(myModifiedSide, true)) return;
if (isStateIsOutOfDate()) return;
String title = e.getPresentation().getText() + " selected changes";
DiffUtil.executeWriteCommand(getDocument(myModifiedSide), e.getProject(), title, new Runnable() {
@Override
public void run() {
// state is invalidated during apply(), but changes are in reverse order, so they should not conflict with each other
apply(selectedChanges);
scheduleRediff();
}
});
}
protected boolean isSomeChangeSelected() {
if (myChangedBlockData == null) return false;
List<UnifiedDiffChange> changes = myChangedBlockData.getDiffChanges();
if (changes.isEmpty()) return false;
List<Caret> carets = getEditor().getCaretModel().getAllCarets();
if (carets.size() != 1) return true;
Caret caret = carets.get(0);
if (caret.hasSelection()) return true;
int line = getEditor().getDocument().getLineNumber(getEditor().getExpectedCaretOffset());
for (UnifiedDiffChange change : changes) {
if (DiffUtil.isSelectedByLine(line, change.getLine1(), change.getLine2())) return true;
}
return false;
}
@CalledWithWriteLock
protected abstract void apply(@NotNull List<UnifiedDiffChange> changes);
}
private class ReplaceSelectedChangesAction extends ApplySelectedChangesActionBase {
public ReplaceSelectedChangesAction(@NotNull Side focusedSide, boolean shortcut) {
super(focusedSide.other(), shortcut);
setShortcutSet(ActionManager.getInstance().getAction(focusedSide.select("Diff.ApplyLeftSide", "Diff.ApplyRightSide")).getShortcutSet());
getTemplatePresentation().setText("Replace");
getTemplatePresentation().setIcon(focusedSide.select(AllIcons.Diff.ArrowRight, AllIcons.Diff.Arrow));
}
@Override
protected void apply(@NotNull List<UnifiedDiffChange> changes) {
for (UnifiedDiffChange change : changes) {
replaceChange(change, myModifiedSide.other());
}
}
}
private class AppendSelectedChangesAction extends ApplySelectedChangesActionBase {
public AppendSelectedChangesAction(@NotNull Side focusedSide, boolean shortcut) {
super(focusedSide.other(), shortcut);
setShortcutSet(ActionManager.getInstance().getAction(focusedSide.select("Diff.AppendLeftSide", "Diff.AppendRightSide")).getShortcutSet());
getTemplatePresentation().setText("Insert");
getTemplatePresentation().setIcon(focusedSide.select(AllIcons.Diff.ArrowRightDown, AllIcons.Diff.ArrowLeftDown));
}
@Override
protected void apply(@NotNull List<UnifiedDiffChange> changes) {
for (UnifiedDiffChange change : changes) {
appendChange(change, myModifiedSide.other());
}
}
}
private class RevertSelectedChangesAction extends ApplySelectedChangesActionBase {
public RevertSelectedChangesAction(@NotNull Side focusedSide) {
super(focusedSide, false);
getTemplatePresentation().setText("Revert");
getTemplatePresentation().setIcon(AllIcons.Diff.Remove);
}
@Override
protected void apply(@NotNull List<UnifiedDiffChange> changes) {
for (UnifiedDiffChange change : changes) {
replaceChange(change, myModifiedSide.other());
}
}
}
@CalledWithWriteLock
public void replaceChange(@NotNull UnifiedDiffChange change, @NotNull Side sourceSide) {
Side outputSide = sourceSide.other();
Document document1 = getDocument(Side.LEFT);
Document document2 = getDocument(Side.RIGHT);
LineFragment lineFragment = change.getLineFragment();
DiffUtil.applyModification(affectedSide.select(document1, document2),
affectedSide.getStartLine(lineFragment), affectedSide.getEndLine(lineFragment),
DiffUtil.applyModification(outputSide.select(document1, document2),
outputSide.getStartLine(lineFragment), outputSide.getEndLine(lineFragment),
sourceSide.select(document1, document2),
sourceSide.getStartLine(lineFragment), sourceSide.getEndLine(lineFragment));
// no need to mark myStateIsOutOfDate - it will be made by DocumentListener
// TODO: we can apply change manually, without marking state out-of-date. But we'll have to schedule rediff anyway.
scheduleRediff();
}
@CalledWithWriteLock
public void appendChange(@NotNull UnifiedDiffChange change, @NotNull final Side sourceSide) {
Side outputSide = sourceSide.other();
Document document1 = getDocument(Side.LEFT);
Document document2 = getDocument(Side.RIGHT);
LineFragment lineFragment = change.getLineFragment();
if (sourceSide.getStartLine(lineFragment) == sourceSide.getEndLine(lineFragment)) return;
DiffUtil.applyModification(outputSide.select(document1, document2),
outputSide.getEndLine(lineFragment), outputSide.getEndLine(lineFragment),
sourceSide.select(document1, document2),
sourceSide.getStartLine(lineFragment), sourceSide.getEndLine(lineFragment));
}
//
@@ -775,6 +927,26 @@ public class UnifiedDiffViewer extends ListenerDiffViewerBase {
return null;
}
@NotNull
@CalledInAwt
private List<UnifiedDiffChange> getSelectedChanges() {
if (myChangedBlockData == null) return Collections.emptyList();
final BitSet lines = DiffUtil.getSelectedLines(myEditor);
List<UnifiedDiffChange> changes = myChangedBlockData.getDiffChanges();
List<UnifiedDiffChange> affectedChanges = new ArrayList<UnifiedDiffChange>();
for (int i = changes.size() - 1; i >= 0; i--) {
UnifiedDiffChange change = changes.get(i);
int line1 = change.getLine1();
int line2 = change.getLine2();
if (DiffUtil.isSelectedByLine(lines, line1, line2)) {
affectedChanges.add(change);
}
}
return affectedChanges;
}
@CalledInAwt
@Nullable
protected OpenFileDescriptor getOpenFileDescriptor(int offset) {
@@ -30,10 +30,7 @@ import com.intellij.diff.util.*;
import com.intellij.diff.util.DiffUserDataKeysEx.ScrollToPolicy;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.Separator;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.diff.DiffNavigationContext;
@@ -84,6 +81,11 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
myFoldingModel = new MyFoldingModel(getEditors(), this);
myModifierProvider = new ModifierProvider();
DiffUtil.registerAction(new ReplaceSelectedChangesAction(Side.LEFT, true), myPanel);
DiffUtil.registerAction(new AppendSelectedChangesAction(Side.LEFT, true), myPanel);
DiffUtil.registerAction(new ReplaceSelectedChangesAction(Side.RIGHT, true), myPanel);
DiffUtil.registerAction(new AppendSelectedChangesAction(Side.RIGHT, true), myPanel);
}
@Override
@@ -137,9 +139,12 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
protected List<AnAction> createEditorPopupActions() {
List<AnAction> group = new ArrayList<AnAction>();
group.add(new ReplaceSelectedChangesAction());
group.add(new AppendSelectedChangesAction());
group.add(new RevertSelectedChangesAction());
group.add(new ReplaceSelectedChangesAction(Side.LEFT, false));
group.add(new AppendSelectedChangesAction(Side.LEFT, false));
group.add(new ReplaceSelectedChangesAction(Side.RIGHT, false));
group.add(new AppendSelectedChangesAction(Side.RIGHT, false));
group.add(new RevertSelectedChangesAction(Side.LEFT));
group.add(new RevertSelectedChangesAction(Side.RIGHT));
group.add(Separator.getInstance());
group.addAll(super.createEditorPopupActions());
@@ -491,18 +496,22 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
//
private abstract class ApplySelectedChangesActionBase extends AnAction implements DumbAware {
private final boolean myModifyOpposite;
@NotNull protected final Side myModifiedSide;
private final boolean myShortcut;
public ApplySelectedChangesActionBase(@Nullable String text,
@Nullable String description,
@Nullable Icon icon,
boolean modifyOpposite) {
super(text, description, icon);
myModifyOpposite = modifyOpposite;
public ApplySelectedChangesActionBase(@NotNull Side modifiedSide, boolean shortcut) {
myModifiedSide = modifiedSide;
myShortcut = shortcut;
}
@Override
public void update(@NotNull AnActionEvent e) {
if (myShortcut) {
// consume shortcut even if there are nothing to do - avoid calling some other action
e.getPresentation().setEnabledAndVisible(true);
return;
}
Editor editor = e.getData(CommonDataKeys.EDITOR);
if (editor != getEditor1() && editor != getEditor2()) {
e.getPresentation().setEnabledAndVisible(false);
@@ -510,30 +519,38 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
}
Side side = Side.fromLeft(editor == getEditor(Side.LEFT));
Editor modifiedEditor = getEditor(side.other(myModifyOpposite));
if (!isVisible(side)) {
e.getPresentation().setEnabledAndVisible(false);
return;
}
Editor modifiedEditor = getEditor(myModifiedSide);
if (!DiffUtil.isEditable(modifiedEditor)) {
e.getPresentation().setEnabledAndVisible(false);
return;
}
e.getPresentation().setIcon(getIcon(side));
e.getPresentation().setVisible(true);
e.getPresentation().setEnabled(isSomeChangeSelected(side));
}
@Override
public void actionPerformed(@NotNull final AnActionEvent e) {
Editor editor = e.getRequiredData(CommonDataKeys.EDITOR);
Editor editor = e.getData(CommonDataKeys.EDITOR);
if (editor != getEditor1() && editor != getEditor2()) return;
final Side side = Side.fromLeft(editor == getEditor(Side.LEFT));
final List<SimpleDiffChange> selectedChanges = getSelectedChanges(side);
if (selectedChanges.isEmpty()) return;
Editor modifiedEditor = getEditor(myModifiedSide);
if (!DiffUtil.isEditable(modifiedEditor)) return;
Editor modifiedEditor = getEditor(side.other(myModifyOpposite));
String title = e.getPresentation().getText() + " selected changes";
DiffUtil.executeWriteCommand(modifiedEditor.getDocument(), e.getProject(), title, new Runnable() {
@Override
public void run() {
apply(side, selectedChanges);
apply(selectedChanges);
}
});
}
@@ -554,66 +571,72 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
return false;
}
@NotNull
protected abstract Icon getIcon(@NotNull Side side);
protected abstract boolean isVisible(@NotNull Side side);
@CalledWithWriteLock
protected abstract void apply(@NotNull Side side, @NotNull List<SimpleDiffChange> changes);
protected abstract void apply(@NotNull List<SimpleDiffChange> changes);
}
private class ReplaceSelectedChangesAction extends ApplySelectedChangesActionBase {
public ReplaceSelectedChangesAction() {
super("Replace", null, AllIcons.Diff.Arrow, true);
}
public ReplaceSelectedChangesAction(@NotNull Side focusedSide, boolean shortcut) {
super(focusedSide.other(), shortcut);
@NotNull
@Override
protected Icon getIcon(@NotNull Side side) {
return side.isLeft() ? AllIcons.Diff.ArrowRight : AllIcons.Diff.Arrow;
setShortcutSet(ActionManager.getInstance().getAction(focusedSide.select("Diff.ApplyLeftSide", "Diff.ApplyRightSide")).getShortcutSet());
getTemplatePresentation().setText("Replace");
getTemplatePresentation().setIcon(focusedSide.select(AllIcons.Diff.ArrowRight, AllIcons.Diff.Arrow));
}
@Override
protected void apply(@NotNull Side side, @NotNull List<SimpleDiffChange> changes) {
protected boolean isVisible(@NotNull Side side) {
return side == myModifiedSide.other();
}
@Override
protected void apply(@NotNull List<SimpleDiffChange> changes) {
for (SimpleDiffChange change : changes) {
replaceChange(change, side);
replaceChange(change, myModifiedSide.other());
}
}
}
private class AppendSelectedChangesAction extends ApplySelectedChangesActionBase {
public AppendSelectedChangesAction() {
super("Insert", null, AllIcons.Diff.ArrowLeftDown, true);
}
public AppendSelectedChangesAction(@NotNull Side focusedSide, boolean shortcut) {
super(focusedSide.other(), shortcut);
@NotNull
@Override
protected Icon getIcon(@NotNull Side side) {
return side.isLeft() ? AllIcons.Diff.ArrowRightDown : AllIcons.Diff.ArrowLeftDown;
setShortcutSet(ActionManager.getInstance().getAction(focusedSide.select("Diff.AppendLeftSide", "Diff.AppendRightSide")).getShortcutSet());
getTemplatePresentation().setText("Insert");
getTemplatePresentation().setIcon(focusedSide.select(AllIcons.Diff.ArrowRightDown, AllIcons.Diff.ArrowLeftDown));
}
@Override
protected void apply(@NotNull Side side, @NotNull List<SimpleDiffChange> changes) {
protected boolean isVisible(@NotNull Side side) {
return side == myModifiedSide.other();
}
@Override
protected void apply(@NotNull List<SimpleDiffChange> changes) {
for (SimpleDiffChange change : changes) {
appendChange(change, side);
appendChange(change, myModifiedSide.other());
}
}
}
private class RevertSelectedChangesAction extends ApplySelectedChangesActionBase {
public RevertSelectedChangesAction() {
super("Revert", null, AllIcons.Diff.Remove, false);
}
@NotNull
@Override
protected Icon getIcon(@NotNull Side side) {
return AllIcons.Diff.Remove;
public RevertSelectedChangesAction(@NotNull Side focusedSide) {
super(focusedSide, false);
getTemplatePresentation().setText("Revert");
getTemplatePresentation().setIcon(AllIcons.Diff.Remove);
}
@Override
protected void apply(@NotNull Side side, @NotNull List<SimpleDiffChange> changes) {
protected boolean isVisible(@NotNull Side side) {
return side == myModifiedSide;
}
@Override
protected void apply(@NotNull List<SimpleDiffChange> changes) {
for (SimpleDiffChange change : changes) {
replaceChange(change, side.other());
replaceChange(change, myModifiedSide.other());
}
}
}
@@ -15,28 +15,25 @@
*/
package com.intellij.diff.tools.simple;
import com.intellij.diff.comparison.ComparisonManager;
import com.intellij.diff.comparison.ComparisonPolicy;
import com.intellij.diff.fragments.MergeLineFragment;
import com.intellij.diff.util.*;
import com.intellij.diff.util.DiffDrawUtil;
import com.intellij.diff.util.DiffUtil;
import com.intellij.diff.util.TextDiffType;
import com.intellij.diff.util.ThreeSide;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.DocumentEx;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.markup.RangeHighlighter;
import com.intellij.openapi.editor.markup.SeparatorPlacement;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
public class SimpleThreesideDiffChange {
@NotNull private final MergeLineFragment myFragment;
public class SimpleThreesideDiffChange extends ThreesideDiffChangeBase {
@NotNull private final List<? extends EditorEx> myEditors;
@NotNull private ConflictType myType;
@NotNull private final MergeLineFragment myFragment;
@NotNull private final List<RangeHighlighter> myHighlighters = new ArrayList<RangeHighlighter>();
@@ -46,10 +43,9 @@ public class SimpleThreesideDiffChange {
public SimpleThreesideDiffChange(@NotNull MergeLineFragment fragment,
@NotNull List<? extends EditorEx> editors,
@NotNull ComparisonPolicy policy) {
myFragment = fragment;
super(fragment, editors, policy);
myEditors = editors;
myType = calcType(fragment, editors, policy);
myFragment = fragment;
installHighlighter();
}
@@ -58,8 +54,8 @@ public class SimpleThreesideDiffChange {
assert myHighlighters.isEmpty();
createHighlighter(ThreeSide.BASE);
if (myType.isLeftChange()) createHighlighter(ThreeSide.LEFT);
if (myType.isRightChange()) createHighlighter(ThreeSide.RIGHT);
if (getType().isLeftChange()) createHighlighter(ThreeSide.LEFT);
if (getType().isRightChange()) createHighlighter(ThreeSide.RIGHT);
}
public void destroyHighlighter() {
@@ -107,24 +103,16 @@ public class SimpleThreesideDiffChange {
// Getters
//
@Override
public int getStartLine(@NotNull ThreeSide side) {
return myFragment.getStartLine(side) + side.select(myLineStartShifts);
}
@Override
public int getEndLine(@NotNull ThreeSide side) {
return myFragment.getEndLine(side) + side.select(myLineEndShifts);
}
@NotNull
public TextDiffType getDiffType() {
return myType.getDiffType();
}
@NotNull
public ConflictType getType() {
return myType;
}
//
// Shift
//
@@ -140,125 +128,4 @@ public class SimpleThreesideDiffChange {
return newRange.damaged;
}
//
// Type
//
@NotNull
private static ConflictType calcType(@NotNull MergeLineFragment fragment,
@NotNull List<? extends EditorEx> editors,
@NotNull ComparisonPolicy policy) {
boolean isLeftEmpty = isIntervalEmpty(fragment, ThreeSide.LEFT);
boolean isBaseEmpty = isIntervalEmpty(fragment, ThreeSide.BASE);
boolean isRightEmpty = isIntervalEmpty(fragment, ThreeSide.RIGHT);
assert !isLeftEmpty || !isBaseEmpty || !isRightEmpty;
if (isBaseEmpty) {
if (isLeftEmpty) { // --=
return new ConflictType(TextDiffType.INSERTED, false, true);
}
else if (isRightEmpty) { // =--
return new ConflictType(TextDiffType.INSERTED, true, false);
}
else { // =-=
boolean equalModifications = compareLeftAndRight(fragment, editors, policy);
return new ConflictType(equalModifications ? TextDiffType.INSERTED : TextDiffType.CONFLICT);
}
}
else {
if (isLeftEmpty && isRightEmpty) { // -=-
return new ConflictType(TextDiffType.DELETED);
}
else { // -==, ==-, ===
boolean unchangedLeft = compareWithBase(fragment, editors, ThreeSide.LEFT);
boolean unchangedRight = compareWithBase(fragment, editors, ThreeSide.RIGHT);
assert !unchangedLeft || !unchangedRight;
if (unchangedLeft) return new ConflictType(isRightEmpty ? TextDiffType.DELETED : TextDiffType.MODIFIED, false, true);
if (unchangedRight) return new ConflictType(isLeftEmpty ? TextDiffType.DELETED : TextDiffType.MODIFIED, true, false);
boolean equalModifications = compareLeftAndRight(fragment, editors, policy);
return new ConflictType(equalModifications ? TextDiffType.MODIFIED : TextDiffType.CONFLICT);
}
}
}
private static boolean compareLeftAndRight(@NotNull MergeLineFragment fragment,
@NotNull List<? extends EditorEx> editors,
@NotNull ComparisonPolicy policy) {
CharSequence content1 = getRangeContent(fragment, editors, ThreeSide.LEFT);
CharSequence content2 = getRangeContent(fragment, editors, ThreeSide.RIGHT);
if (policy == ComparisonPolicy.IGNORE_WHITESPACES) {
if (content1 == null) content1 = "";
if (content2 == null) content2 = "";
}
if (content1 == null && content2 == null) return true;
if (content1 == null ^ content2 == null) return false;
return ComparisonManager.getInstance().isEquals(content1, content2, policy);
}
private static boolean compareWithBase(@NotNull MergeLineFragment fragment,
@NotNull List<? extends EditorEx> editors,
@NotNull ThreeSide side) {
CharSequence content1 = getRangeContent(fragment, editors, ThreeSide.BASE);
CharSequence content2 = getRangeContent(fragment, editors, side);
return StringUtil.equals(content1, content2);
}
@Nullable
private static CharSequence getRangeContent(@NotNull MergeLineFragment fragment,
@NotNull List<? extends EditorEx> editors,
@NotNull ThreeSide side) {
DocumentEx document = side.select(editors).getDocument();
int line1 = fragment.getStartLine(side);
int line2 = fragment.getEndLine(side);
if (line1 == line2) return null;
return DiffUtil.getLinesContent(document, line1, line2);
}
private static boolean isIntervalEmpty(@NotNull MergeLineFragment fragment, @NotNull ThreeSide side) {
return fragment.getStartLine(side) == fragment.getEndLine(side);
}
//
// Helpers
//
public static class ConflictType {
@NotNull private final TextDiffType myType;
private final boolean myLeftChange;
private final boolean myRightChange;
public ConflictType(@NotNull TextDiffType type) {
this(type, true, true);
}
public ConflictType(@NotNull TextDiffType type, boolean leftChange, boolean rightChange) {
myType = type;
myLeftChange = leftChange;
myRightChange = rightChange;
}
@NotNull
public TextDiffType getDiffType() {
return myType;
}
public boolean isLeftChange() {
return myLeftChange;
}
public boolean isRightChange() {
return myRightChange;
}
public boolean isChange(@NotNull Side side) {
return side.isLeft() ? myLeftChange : myRightChange;
}
}
}
@@ -26,7 +26,7 @@ import com.intellij.diff.contents.DocumentContent;
import com.intellij.diff.fragments.MergeLineFragment;
import com.intellij.diff.requests.ContentDiffRequest;
import com.intellij.diff.requests.DiffRequest;
import com.intellij.diff.tools.util.*;
import com.intellij.diff.tools.util.DiffNotifications;
import com.intellij.diff.tools.util.base.IgnorePolicy;
import com.intellij.diff.tools.util.base.TextDiffViewerUtil;
import com.intellij.diff.tools.util.side.ThreesideTextDiffViewer;
@@ -34,79 +34,31 @@ import com.intellij.diff.util.DiffDividerDrawUtil;
import com.intellij.diff.util.DiffUtil;
import com.intellij.diff.util.Side;
import com.intellij.diff.util.ThreeSide;
import com.intellij.diff.util.*;
import com.intellij.diff.util.DiffUserDataKeysEx.ScrollToPolicy;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.Separator;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.diff.DiffBundle;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.UserDataHolder;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.ButtonlessScrollBarUI;
import org.jetbrains.annotations.CalledInAwt;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
// TODO: extract common methods with Twoside
public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewer {
public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewerEx {
public static final Logger LOG = Logger.getInstance(SimpleThreesideDiffViewer.class);
@NotNull private final SyncScrollSupport.SyncScrollable mySyncScrollable1;
@NotNull private final SyncScrollSupport.SyncScrollable mySyncScrollable2;
@NotNull private final PrevNextDifferenceIterable myPrevNextDifferenceIterable;
@NotNull private final MyStatusPanel myStatusPanel;
@NotNull private final List<SimpleThreesideDiffChange> myDiffChanges = new ArrayList<SimpleThreesideDiffChange>();
@NotNull private final List<SimpleThreesideDiffChange> myInvalidDiffChanges = new ArrayList<SimpleThreesideDiffChange>();
private int myChangesCount = -1;
private int myConflictsCount = -1;
@NotNull private final MyFoldingModel myFoldingModel;
@NotNull private final MyInitialScrollHelper myInitialScrollHelper = new MyInitialScrollHelper();
public SimpleThreesideDiffViewer(@NotNull DiffContext context, @NotNull DiffRequest request) {
super(context, (ContentDiffRequest)request);
mySyncScrollable1 = new MySyncScrollable(Side.LEFT);
mySyncScrollable2 = new MySyncScrollable(Side.RIGHT);
myPrevNextDifferenceIterable = new MyPrevNextDifferenceIterable();
myStatusPanel = new MyStatusPanel();
myFoldingModel = new MyFoldingModel(getEditors().toArray(new EditorEx[3]), this);
}
@Override
@CalledInAwt
protected void onInit() {
super.onInit();
myContentPanel.setPainter(new MyDividerPainter(Side.LEFT), Side.LEFT);
myContentPanel.setPainter(new MyDividerPainter(Side.RIGHT), Side.RIGHT);
myContentPanel.setScrollbarPainter(new MyScrollbarPainter());
}
@Override
@CalledInAwt
protected void onDispose() {
destroyChangedBlocks();
super.onDispose();
}
@NotNull
@@ -145,30 +97,10 @@ public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewer {
return group;
}
@Override
@CalledInAwt
protected void processContextHints() {
super.processContextHints();
myInitialScrollHelper.processContext(myRequest);
}
@Override
@CalledInAwt
protected void updateContextHints() {
super.updateContextHints();
myFoldingModel.updateContext(myRequest, getFoldingModelSettings());
myInitialScrollHelper.updateContext(myRequest);
}
//
// Diff
//
@NotNull
public FoldingModelSupport.Settings getFoldingModelSettings() {
return TextDiffViewerUtil.getFoldingModelSettings(myContext);
}
@Override
protected void onSlowRediff() {
super.onSlowRediff();
@@ -227,13 +159,11 @@ public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewer {
myFoldingModel.updateContext(myRequest, getFoldingModelSettings());
clearDiffPresentation();
myChangesCount = 0;
myConflictsCount = 0;
resetChangeCounters();
for (MergeLineFragment fragment : fragments) {
SimpleThreesideDiffChange change = new SimpleThreesideDiffChange(fragment, getEditors(), comparisonPolicy);
myDiffChanges.add(change);
if (change.getDiffType() != TextDiffType.CONFLICT) myChangesCount++;
if (change.getDiffType() == TextDiffType.CONFLICT) myConflictsCount++;
onChangeAdded(change);
}
myFoldingModel.install(fragments, myRequest, getFoldingModelSettings());
@@ -246,24 +176,8 @@ public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewer {
};
}
@NotNull
private Runnable applyNotification(@Nullable final JComponent notification) {
return new Runnable() {
@Override
public void run() {
clearDiffPresentation();
if (notification != null) myPanel.addNotification(notification);
}
};
}
private void clearDiffPresentation() {
myStatusPanel.setBusy(false);
myPanel.resetNotifications();
destroyChangedBlocks();
}
private void destroyChangedBlocks() {
protected void destroyChangedBlocks() {
super.destroyChangedBlocks();
for (SimpleThreesideDiffChange change : myDiffChanges) {
change.destroyHighlighter();
}
@@ -273,14 +187,6 @@ public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewer {
change.destroyHighlighter();
}
myInvalidDiffChanges.clear();
myChangesCount = -1;
myConflictsCount = -1;
myFoldingModel.destroy();
myContentPanel.repaintDividers();
myStatusPanel.update();
}
//
@@ -319,28 +225,6 @@ public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewer {
}
}
@Override
protected void onDocumentChange(@NotNull DocumentEvent e) {
super.onDocumentChange(e);
myFoldingModel.onDocumentChanged(e);
}
@CalledInAwt
protected boolean doScrollToChange(@NotNull ScrollToPolicy scrollToPolicy) {
SimpleThreesideDiffChange targetChange = scrollToPolicy.select(myDiffChanges);
if (targetChange == null) return false;
doScrollToChange(targetChange, false);
return true;
}
private void doScrollToChange(@NotNull SimpleThreesideDiffChange change, boolean animated) {
// TODO: use anchors to fix scrolling issue at the start/end of file
EditorEx editor = getCurrentEditor();
int line = change.getStartLine(getCurrentSide());
DiffUtil.scrollEditor(editor, line, animated);
}
@NotNull
private IgnorePolicy getIgnorePolicy() {
IgnorePolicy policy = getTextSettings().getIgnorePolicy();
@@ -353,41 +237,20 @@ public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewer {
//
@NotNull
protected List<SimpleThreesideDiffChange> getDiffChanges() {
public List<SimpleThreesideDiffChange> getChanges() {
return myDiffChanges;
}
@NotNull
@Override
protected SyncScrollSupport.SyncScrollable getSyncScrollable(@NotNull Side side) {
return side.select(mySyncScrollable1, mySyncScrollable2);
}
@NotNull
@Override
protected JComponent getStatusPanel() {
return myStatusPanel;
protected DiffDividerDrawUtil.DividerPaintable getDividerPaintable(@NotNull Side side) {
return new MyDividerPaintable(side);
}
//
// Misc
//
@Nullable
@CalledInAwt
private SimpleThreesideDiffChange getSelectedChange(@NotNull ThreeSide side) {
EditorEx editor = getEditor(side);
int caretLine = editor.getCaretModel().getLogicalPosition().line;
for (SimpleThreesideDiffChange change : myDiffChanges) {
int line1 = change.getStartLine(side);
int line2 = change.getEndLine(side);
if (DiffUtil.isSelectedByLine(caretLine, line1, line2)) return change;
}
return null;
}
@SuppressWarnings("MethodOverridesStaticMethodOfSuperclass")
public static boolean canShowRequest(@NotNull DiffContext context, @NotNull DiffRequest request) {
return ThreesideTextDiffViewer.canShowRequest(context, request);
@@ -397,46 +260,6 @@ public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewer {
// Actions
//
private class MyPrevNextDifferenceIterable extends PrevNextDifferenceIterableBase<SimpleThreesideDiffChange> {
@NotNull
@Override
protected List<SimpleThreesideDiffChange> getChanges() {
return myDiffChanges;
}
@NotNull
@Override
protected EditorEx getEditor() {
return getCurrentEditor();
}
@Override
protected int getStartLine(@NotNull SimpleThreesideDiffChange change) {
return change.getStartLine(getCurrentSide());
}
@Override
protected int getEndLine(@NotNull SimpleThreesideDiffChange change) {
return change.getEndLine(getCurrentSide());
}
@Override
protected void scrollToChange(@NotNull SimpleThreesideDiffChange change) {
doScrollToChange(change, true);
}
}
private class MyToggleExpandByDefaultAction extends TextDiffViewerUtil.ToggleExpandByDefaultAction {
public MyToggleExpandByDefaultAction() {
super(getTextSettings());
}
@Override
protected void expandAll(boolean expand) {
myFoldingModel.expandAll(expand);
}
}
private class MyIgnorePolicySettingAction extends TextDiffViewerUtil.IgnorePolicySettingAction {
public MyIgnorePolicySettingAction() {
super(getTextSettings());
@@ -472,48 +295,6 @@ public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewer {
// Helpers
//
@Nullable
@Override
public Object getData(@NonNls String dataId) {
if (DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.is(dataId)) {
return myPrevNextDifferenceIterable;
}
else if (DiffDataKeys.CURRENT_CHANGE_RANGE.is(dataId)) {
SimpleThreesideDiffChange change = getSelectedChange(getCurrentSide());
if (change != null) {
return new LineRange(change.getStartLine(getCurrentSide()), change.getEndLine(getCurrentSide()));
}
}
return super.getData(dataId);
}
private class MySyncScrollable extends BaseSyncScrollable {
@NotNull private final Side mySide;
public MySyncScrollable(@NotNull Side side) {
mySide = side;
}
@Override
public boolean isSyncScrollEnabled() {
return getTextSettings().isEnableSyncScroll();
}
@Override
protected void processHelper(@NotNull ScrollHelper helper) {
ThreeSide left = mySide.select(ThreeSide.LEFT, ThreeSide.BASE);
ThreeSide right = mySide.select(ThreeSide.BASE, ThreeSide.RIGHT);
if (!helper.process(0, 0)) return;
for (SimpleThreesideDiffChange diffChange : myDiffChanges) {
if (!helper.process(diffChange.getStartLine(left), diffChange.getStartLine(right))) return;
if (!helper.process(diffChange.getEndLine(left), diffChange.getEndLine(right))) return;
}
helper.process(getEditor(left).getDocument().getLineCount(), getEditor(right).getDocument().getLineCount());
}
}
private class MyDividerPaintable implements DiffDividerDrawUtil.DividerPaintable {
@NotNull private final Side mySide;
@@ -527,7 +308,7 @@ public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewer {
ThreeSide right = mySide.select(ThreeSide.BASE, ThreeSide.RIGHT);
for (SimpleThreesideDiffChange diffChange : myDiffChanges) {
if (!diffChange.getType().isChange(mySide)) continue;
if (!diffChange.isChange(mySide)) continue;
if (!handler.process(diffChange.getStartLine(left), diffChange.getEndLine(left),
diffChange.getStartLine(right), diffChange.getEndLine(right),
diffChange.getDiffType().getColor(getEditor(ThreeSide.BASE)))) {
@@ -536,117 +317,4 @@ public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewer {
}
}
}
private class MyDividerPainter implements DiffSplitter.Painter {
@NotNull private final Side mySide;
@NotNull private final MyDividerPaintable myPaintable;
public MyDividerPainter(@NotNull Side side) {
mySide = side;
myPaintable = new MyDividerPaintable(side);
}
@Override
public void paint(@NotNull Graphics g, @NotNull JComponent divider) {
Graphics2D gg = DiffDividerDrawUtil.getDividerGraphics(g, divider, getEditor(ThreeSide.BASE).getComponent());
gg.setColor(DiffDrawUtil.getDividerColor(getEditor(ThreeSide.BASE)));
gg.fill(gg.getClipBounds());
Editor editor1 = mySide.select(getEditor(ThreeSide.LEFT), getEditor(ThreeSide.BASE));
Editor editor2 = mySide.select(getEditor(ThreeSide.BASE), getEditor(ThreeSide.RIGHT));
//DividerPolygonUtil.paintSimplePolygons(gg, divider.getWidth(), editor1, editor2, myPaintable);
DiffDividerDrawUtil.paintPolygons(gg, divider.getWidth(), editor1, editor2, myPaintable);
myFoldingModel.paintOnDivider(gg, divider, mySide);
gg.dispose();
}
}
private class MyScrollbarPainter implements ButtonlessScrollBarUI.ScrollbarRepaintCallback {
@NotNull private final MyDividerPaintable myPaintable = new MyDividerPaintable(Side.RIGHT);
@Override
public void call(Graphics g) {
EditorEx editor1 = getEditor(ThreeSide.BASE);
EditorEx editor2 = getEditor(ThreeSide.RIGHT);
int width = editor1.getScrollPane().getVerticalScrollBar().getWidth();
DiffDividerDrawUtil.paintPolygonsOnScrollbar((Graphics2D)g, width, editor1, editor2, myPaintable);
myFoldingModel.paintOnScrollbar((Graphics2D)g, width);
}
}
private class MyStatusPanel extends StatusPanel {
@Nullable
@Override
protected String getMessage() {
if (myChangesCount < 0 || myConflictsCount < 0) return null;
if (myChangesCount == 0 && myConflictsCount == 0) {
return DiffBundle.message("merge.dialog.all.conflicts.resolved.message.text");
}
return makeCounterWord(myChangesCount, "change") + ". " + makeCounterWord(myConflictsCount, "conflict");
}
@NotNull
private String makeCounterWord(int number, @NotNull String word) {
if (number == 0) {
return "No " + StringUtil.pluralize(word);
}
return number + " " + StringUtil.pluralize(word, number);
}
}
private static class MyFoldingModel extends FoldingModelSupport {
private final MyPaintable myPaintable1 = new MyPaintable(0, 1);
private final MyPaintable myPaintable2 = new MyPaintable(1, 2);
public MyFoldingModel(@NotNull EditorEx[] editors, @NotNull Disposable disposable) {
super(editors, disposable);
assert editors.length == 3;
}
public void install(@Nullable List<MergeLineFragment> fragments,
@NotNull UserDataHolder context,
@NotNull FoldingModelSupport.Settings settings) {
Iterator<int[]> it = map(fragments, new Function<MergeLineFragment, int[]>() {
@Override
public int[] fun(MergeLineFragment fragment) {
return new int[]{
fragment.getStartLine(ThreeSide.LEFT),
fragment.getEndLine(ThreeSide.LEFT),
fragment.getStartLine(ThreeSide.BASE),
fragment.getEndLine(ThreeSide.BASE),
fragment.getStartLine(ThreeSide.RIGHT),
fragment.getEndLine(ThreeSide.RIGHT)};
}
});
install(it, context, settings);
}
public void paintOnDivider(@NotNull Graphics2D gg, @NotNull Component divider, @NotNull Side side) {
MyPaintable paintable = side.select(myPaintable1, myPaintable2);
paintable.paintOnDivider(gg, divider);
}
public void paintOnScrollbar(@NotNull Graphics2D gg, int width) {
myPaintable2.paintOnScrollbar(gg, width);
}
}
private class MyInitialScrollHelper extends MyInitialScrollPositionHelper {
@Override
protected boolean doScrollToChange() {
if (myScrollToChange == null) return false;
return SimpleThreesideDiffViewer.this.doScrollToChange(myScrollToChange);
}
@Override
protected boolean doScrollToFirstChange() {
return SimpleThreesideDiffViewer.this.doScrollToChange(ScrollToPolicy.FIRST_CHANGE);
}
}
}
@@ -0,0 +1,188 @@
/*
* Copyright 2000-2015 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.diff.tools.simple;
import com.intellij.diff.comparison.ComparisonManager;
import com.intellij.diff.comparison.ComparisonPolicy;
import com.intellij.diff.fragments.MergeLineFragment;
import com.intellij.diff.util.DiffUtil;
import com.intellij.diff.util.Side;
import com.intellij.diff.util.TextDiffType;
import com.intellij.diff.util.ThreeSide;
import com.intellij.openapi.editor.ex.DocumentEx;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public abstract class ThreesideDiffChangeBase {
@NotNull private ConflictType myType;
public ThreesideDiffChangeBase(@NotNull MergeLineFragment fragment,
@NotNull List<? extends EditorEx> editors,
@NotNull ComparisonPolicy policy) {
myType = calcType(fragment, editors, policy);
}
//
// Getters
//
public abstract int getStartLine(@NotNull ThreeSide side);
public abstract int getEndLine(@NotNull ThreeSide side);
@NotNull
public TextDiffType getDiffType() {
return myType.getDiffType();
}
@NotNull
public ConflictType getType() {
return myType;
}
public boolean isConflict() {
return getDiffType() == TextDiffType.CONFLICT;
}
public boolean isChange(@NotNull Side side) {
return myType.isChange(side);
}
//
// Type
//
@NotNull
private static ConflictType calcType(@NotNull MergeLineFragment fragment,
@NotNull List<? extends EditorEx> editors,
@NotNull ComparisonPolicy policy) {
boolean isLeftEmpty = isIntervalEmpty(fragment, ThreeSide.LEFT);
boolean isBaseEmpty = isIntervalEmpty(fragment, ThreeSide.BASE);
boolean isRightEmpty = isIntervalEmpty(fragment, ThreeSide.RIGHT);
assert !isLeftEmpty || !isBaseEmpty || !isRightEmpty;
if (isBaseEmpty) {
if (isLeftEmpty) { // --=
return new ConflictType(TextDiffType.INSERTED, false, true);
}
else if (isRightEmpty) { // =--
return new ConflictType(TextDiffType.INSERTED, true, false);
}
else { // =-=
boolean equalModifications = compareLeftAndRight(fragment, editors, policy);
return new ConflictType(equalModifications ? TextDiffType.INSERTED : TextDiffType.CONFLICT);
}
}
else {
if (isLeftEmpty && isRightEmpty) { // -=-
return new ConflictType(TextDiffType.DELETED);
}
else { // -==, ==-, ===
boolean unchangedLeft = compareWithBase(fragment, editors, ThreeSide.LEFT);
boolean unchangedRight = compareWithBase(fragment, editors, ThreeSide.RIGHT);
assert !unchangedLeft || !unchangedRight;
if (unchangedLeft) return new ConflictType(isRightEmpty ? TextDiffType.DELETED : TextDiffType.MODIFIED, false, true);
if (unchangedRight) return new ConflictType(isLeftEmpty ? TextDiffType.DELETED : TextDiffType.MODIFIED, true, false);
boolean equalModifications = compareLeftAndRight(fragment, editors, policy);
return new ConflictType(equalModifications ? TextDiffType.MODIFIED : TextDiffType.CONFLICT);
}
}
}
private static boolean compareLeftAndRight(@NotNull MergeLineFragment fragment,
@NotNull List<? extends EditorEx> editors,
@NotNull ComparisonPolicy policy) {
CharSequence content1 = getRangeContent(fragment, editors, ThreeSide.LEFT);
CharSequence content2 = getRangeContent(fragment, editors, ThreeSide.RIGHT);
if (policy == ComparisonPolicy.IGNORE_WHITESPACES) {
if (content1 == null) content1 = "";
if (content2 == null) content2 = "";
}
if (content1 == null && content2 == null) return true;
if (content1 == null ^ content2 == null) return false;
return ComparisonManager.getInstance().isEquals(content1, content2, policy);
}
private static boolean compareWithBase(@NotNull MergeLineFragment fragment,
@NotNull List<? extends EditorEx> editors,
@NotNull ThreeSide side) {
CharSequence content1 = getRangeContent(fragment, editors, ThreeSide.BASE);
CharSequence content2 = getRangeContent(fragment, editors, side);
return StringUtil.equals(content1, content2);
}
@Nullable
private static CharSequence getRangeContent(@NotNull MergeLineFragment fragment,
@NotNull List<? extends EditorEx> editors,
@NotNull ThreeSide side) {
DocumentEx document = side.select(editors).getDocument();
int line1 = fragment.getStartLine(side);
int line2 = fragment.getEndLine(side);
if (line1 == line2) return null;
return DiffUtil.getLinesContent(document, line1, line2);
}
private static boolean isIntervalEmpty(@NotNull MergeLineFragment fragment, @NotNull ThreeSide side) {
return fragment.getStartLine(side) == fragment.getEndLine(side);
}
//
// Helpers
//
public static class ConflictType {
@NotNull private final TextDiffType myType;
private final boolean myLeftChange;
private final boolean myRightChange;
public ConflictType(@NotNull TextDiffType type) {
this(type, true, true);
}
public ConflictType(@NotNull TextDiffType type, boolean leftChange, boolean rightChange) {
myType = type;
myLeftChange = leftChange;
myRightChange = rightChange;
}
@NotNull
public TextDiffType getDiffType() {
return myType;
}
public boolean isLeftChange() {
return myLeftChange;
}
public boolean isRightChange() {
return myRightChange;
}
public boolean isChange(@NotNull Side side) {
return side.isLeft() ? myLeftChange : myRightChange;
}
}
}
@@ -0,0 +1,461 @@
/*
* Copyright 2000-2015 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.diff.tools.simple;
import com.intellij.diff.DiffContext;
import com.intellij.diff.fragments.MergeLineFragment;
import com.intellij.diff.requests.ContentDiffRequest;
import com.intellij.diff.tools.util.*;
import com.intellij.diff.tools.util.base.TextDiffViewerUtil;
import com.intellij.diff.tools.util.side.ThreesideTextDiffViewer;
import com.intellij.diff.util.*;
import com.intellij.diff.util.DiffDividerDrawUtil.DividerPaintable;
import com.intellij.diff.util.DiffUserDataKeysEx.ScrollToPolicy;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.diff.DiffBundle;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.util.UserDataHolder;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.Function;
import com.intellij.util.ui.ButtonlessScrollBarUI;
import org.jetbrains.annotations.CalledInAwt;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.util.Iterator;
import java.util.List;
public abstract class ThreesideTextDiffViewerEx extends ThreesideTextDiffViewer {
public static final Logger LOG = Logger.getInstance(ThreesideTextDiffViewerEx.class);
@NotNull private final SyncScrollSupport.SyncScrollable mySyncScrollable1;
@NotNull private final SyncScrollSupport.SyncScrollable mySyncScrollable2;
@NotNull protected final PrevNextDifferenceIterable myPrevNextDifferenceIterable;
@NotNull protected final MyStatusPanel myStatusPanel;
@NotNull protected final MyFoldingModel myFoldingModel;
@NotNull protected final MyInitialScrollHelper myInitialScrollHelper = new MyInitialScrollHelper();
private int myChangesCount = -1;
private int myConflictsCount = -1;
public ThreesideTextDiffViewerEx(@NotNull DiffContext context, @NotNull ContentDiffRequest request) {
super(context, request);
mySyncScrollable1 = new MySyncScrollable(Side.LEFT);
mySyncScrollable2 = new MySyncScrollable(Side.RIGHT);
myPrevNextDifferenceIterable = new MyPrevNextDifferenceIterable();
myStatusPanel = new MyStatusPanel();
myFoldingModel = new MyFoldingModel(getEditors().toArray(new EditorEx[3]), this);
}
@Override
@CalledInAwt
protected void onInit() {
super.onInit();
myContentPanel.setPainter(new MyDividerPainter(Side.LEFT), Side.LEFT);
myContentPanel.setPainter(new MyDividerPainter(Side.RIGHT), Side.RIGHT);
myContentPanel.setScrollbarPainter(new MyScrollbarPainter());
}
@Override
@CalledInAwt
protected void onDispose() {
destroyChangedBlocks();
super.onDispose();
}
@Override
@CalledInAwt
protected void processContextHints() {
super.processContextHints();
myInitialScrollHelper.processContext(myRequest);
}
@Override
@CalledInAwt
protected void updateContextHints() {
super.updateContextHints();
myFoldingModel.updateContext(myRequest, getFoldingModelSettings());
myInitialScrollHelper.updateContext(myRequest);
}
//
// Diff
//
@NotNull
public FoldingModelSupport.Settings getFoldingModelSettings() {
return TextDiffViewerUtil.getFoldingModelSettings(myContext);
}
@NotNull
protected Runnable applyNotification(@Nullable final JComponent notification) {
return new Runnable() {
@Override
public void run() {
clearDiffPresentation();
if (notification != null) myPanel.addNotification(notification);
}
};
}
protected void clearDiffPresentation() {
myStatusPanel.setBusy(false);
myPanel.resetNotifications();
destroyChangedBlocks();
myContentPanel.repaintDividers();
myStatusPanel.update();
}
protected void destroyChangedBlocks() {
myFoldingModel.destroy();
}
//
// Impl
//
@Override
protected void onDocumentChange(@NotNull DocumentEvent e) {
super.onDocumentChange(e);
myFoldingModel.onDocumentChanged(e);
}
@CalledInAwt
protected boolean doScrollToChange(@NotNull ScrollToPolicy scrollToPolicy) {
ThreesideDiffChangeBase targetChange = scrollToPolicy.select(getChanges());
if (targetChange == null) return false;
doScrollToChange(targetChange, false);
return true;
}
protected void doScrollToChange(@NotNull ThreesideDiffChangeBase change, boolean animated) {
int[] startLines = new int[3];
int[] endLines = new int[3];
for (int i = 0; i < 3; i++) {
ThreeSide side = ThreeSide.fromIndex(i);
startLines[i] = change.getStartLine(side);
endLines[i] = change.getEndLine(side);
DiffUtil.moveCaret(getEditor(side), startLines[i]);
}
getSyncScrollSupport().makeVisible(getCurrentSide(), startLines, endLines, animated);
}
//
// Counters
//
public int getChangesCount() {
return myChangesCount;
}
public int getConflictsCount() {
return myConflictsCount;
}
protected void resetChangeCounters() {
myChangesCount = 0;
myConflictsCount = 0;
}
protected void onChangeAdded(@NotNull ThreesideDiffChangeBase change) {
if (change.isConflict()) {
myConflictsCount++;
}
else {
myChangesCount++;
}
myStatusPanel.update();
}
protected void onChangeRemoved(@NotNull ThreesideDiffChangeBase change) {
if (change.isConflict()) {
myConflictsCount--;
}
else {
myChangesCount--;
}
myStatusPanel.update();
}
//
// Getters
//
@NotNull
protected abstract DividerPaintable getDividerPaintable(@NotNull Side side);
/*
* Some changes (ex: applied ones) can be excluded from general processing, but should be painted/used for synchronized scrolling
*/
@NotNull
protected List<? extends ThreesideDiffChangeBase> getAllChanges() {
return getChanges();
}
@NotNull
protected abstract List<? extends ThreesideDiffChangeBase> getChanges();
@NotNull
@Override
protected SyncScrollSupport.SyncScrollable getSyncScrollable(@NotNull Side side) {
return side.select(mySyncScrollable1, mySyncScrollable2);
}
@NotNull
@Override
protected JComponent getStatusPanel() {
return myStatusPanel;
}
@NotNull
public SyncScrollSupport.ThreesideSyncScrollSupport getSyncScrollSupport() {
//noinspection ConstantConditions
return mySyncScrollSupport;
}
//
// Misc
//
@Nullable
@CalledInAwt
protected ThreesideDiffChangeBase getSelectedChange(@NotNull ThreeSide side) {
int caretLine = getEditor(side).getCaretModel().getLogicalPosition().line;
for (ThreesideDiffChangeBase change : getChanges()) {
int line1 = change.getStartLine(side);
int line2 = change.getEndLine(side);
if (DiffUtil.isSelectedByLine(caretLine, line1, line2)) return change;
}
return null;
}
//
// Actions
//
protected class MyPrevNextDifferenceIterable extends PrevNextDifferenceIterableBase<ThreesideDiffChangeBase> {
@NotNull
@Override
protected List<? extends ThreesideDiffChangeBase> getChanges() {
return ThreesideTextDiffViewerEx.this.getChanges();
}
@NotNull
@Override
protected EditorEx getEditor() {
return getCurrentEditor();
}
@Override
protected int getStartLine(@NotNull ThreesideDiffChangeBase change) {
return change.getStartLine(getCurrentSide());
}
@Override
protected int getEndLine(@NotNull ThreesideDiffChangeBase change) {
return change.getEndLine(getCurrentSide());
}
@Override
protected void scrollToChange(@NotNull ThreesideDiffChangeBase change) {
doScrollToChange(change, true);
}
}
protected class MyToggleExpandByDefaultAction extends TextDiffViewerUtil.ToggleExpandByDefaultAction {
public MyToggleExpandByDefaultAction() {
super(getTextSettings());
}
@Override
protected void expandAll(boolean expand) {
myFoldingModel.expandAll(expand);
}
}
//
// Helpers
//
@Nullable
@Override
public Object getData(@NonNls String dataId) {
if (DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.is(dataId)) {
return myPrevNextDifferenceIterable;
}
else if (DiffDataKeys.CURRENT_CHANGE_RANGE.is(dataId)) {
ThreesideDiffChangeBase change = getSelectedChange(getCurrentSide());
if (change != null) {
return new LineRange(change.getStartLine(getCurrentSide()), change.getEndLine(getCurrentSide()));
}
}
return super.getData(dataId);
}
protected class MySyncScrollable extends BaseSyncScrollable {
@NotNull private final Side mySide;
public MySyncScrollable(@NotNull Side side) {
mySide = side;
}
@Override
public boolean isSyncScrollEnabled() {
return getTextSettings().isEnableSyncScroll();
}
@Override
protected void processHelper(@NotNull ScrollHelper helper) {
ThreeSide left = mySide.select(ThreeSide.LEFT, ThreeSide.BASE);
ThreeSide right = mySide.select(ThreeSide.BASE, ThreeSide.RIGHT);
if (!helper.process(0, 0)) return;
for (ThreesideDiffChangeBase diffChange : getAllChanges()) {
if (!helper.process(diffChange.getStartLine(left), diffChange.getStartLine(right))) return;
if (!helper.process(diffChange.getEndLine(left), diffChange.getEndLine(right))) return;
}
helper.process(getEditor(left).getDocument().getLineCount(), getEditor(right).getDocument().getLineCount());
}
}
protected class MyDividerPainter implements DiffSplitter.Painter {
@NotNull private final Side mySide;
@NotNull private final DividerPaintable myPaintable;
public MyDividerPainter(@NotNull Side side) {
mySide = side;
myPaintable = getDividerPaintable(side);
}
@Override
public void paint(@NotNull Graphics g, @NotNull JComponent divider) {
Graphics2D gg = DiffDividerDrawUtil.getDividerGraphics(g, divider, getEditor(ThreeSide.BASE).getComponent());
gg.setColor(DiffDrawUtil.getDividerColor(getEditor(ThreeSide.BASE)));
gg.fill(gg.getClipBounds());
Editor editor1 = mySide.select(getEditor(ThreeSide.LEFT), getEditor(ThreeSide.BASE));
Editor editor2 = mySide.select(getEditor(ThreeSide.BASE), getEditor(ThreeSide.RIGHT));
//DividerPolygonUtil.paintSimplePolygons(gg, divider.getWidth(), editor1, editor2, myPaintable);
DiffDividerDrawUtil.paintPolygons(gg, divider.getWidth(), editor1, editor2, myPaintable);
myFoldingModel.paintOnDivider(gg, divider, mySide);
gg.dispose();
}
}
protected class MyScrollbarPainter implements ButtonlessScrollBarUI.ScrollbarRepaintCallback {
@NotNull private final DividerPaintable myPaintable = getDividerPaintable(Side.RIGHT);
@Override
public void call(Graphics g) {
EditorEx editor1 = getEditor(ThreeSide.BASE);
EditorEx editor2 = getEditor(ThreeSide.RIGHT);
int width = editor1.getScrollPane().getVerticalScrollBar().getWidth();
DiffDividerDrawUtil.paintPolygonsOnScrollbar((Graphics2D)g, width, editor1, editor2, myPaintable);
myFoldingModel.paintOnScrollbar((Graphics2D)g, width);
}
}
protected class MyStatusPanel extends StatusPanel {
@Nullable
@Override
protected String getMessage() {
if (myChangesCount < 0 || myConflictsCount < 0) return null;
if (myChangesCount == 0 && myConflictsCount == 0) {
return DiffBundle.message("merge.dialog.all.conflicts.resolved.message.text");
}
return makeCounterWord(myChangesCount, "change") + ". " + makeCounterWord(myConflictsCount, "conflict");
}
@NotNull
private String makeCounterWord(int number, @NotNull String word) {
if (number == 0) {
return "No " + StringUtil.pluralize(word);
}
return number + " " + StringUtil.pluralize(word, number);
}
}
protected static class MyFoldingModel extends FoldingModelSupport {
private final MyPaintable myPaintable1 = new MyPaintable(0, 1);
private final MyPaintable myPaintable2 = new MyPaintable(1, 2);
public MyFoldingModel(@NotNull EditorEx[] editors, @NotNull Disposable disposable) {
super(editors, disposable);
assert editors.length == 3;
}
public void install(@Nullable List<MergeLineFragment> fragments,
@NotNull UserDataHolder context,
@NotNull FoldingModelSupport.Settings settings) {
Iterator<int[]> it = map(fragments, new Function<MergeLineFragment, int[]>() {
@Override
public int[] fun(MergeLineFragment fragment) {
return new int[]{
fragment.getStartLine(ThreeSide.LEFT),
fragment.getEndLine(ThreeSide.LEFT),
fragment.getStartLine(ThreeSide.BASE),
fragment.getEndLine(ThreeSide.BASE),
fragment.getStartLine(ThreeSide.RIGHT),
fragment.getEndLine(ThreeSide.RIGHT)};
}
});
install(it, context, settings);
}
public void paintOnDivider(@NotNull Graphics2D gg, @NotNull Component divider, @NotNull Side side) {
MyPaintable paintable = side.select(myPaintable1, myPaintable2);
paintable.paintOnDivider(gg, divider);
}
public void paintOnScrollbar(@NotNull Graphics2D gg, int width) {
myPaintable2.paintOnScrollbar(gg, width);
}
}
protected class MyInitialScrollHelper extends MyInitialScrollPositionHelper {
@Override
protected boolean doScrollToChange() {
if (myScrollToChange == null) return false;
return ThreesideTextDiffViewerEx.this.doScrollToChange(myScrollToChange);
}
@Override
protected boolean doScrollToFirstChange() {
return ThreesideTextDiffViewerEx.this.doScrollToChange(ScrollToPolicy.FIRST_CHANGE);
}
}
}
@@ -22,7 +22,7 @@ import java.util.List;
public abstract class PrevNextDifferenceIterableBase<T> implements PrevNextDifferenceIterable {
@NotNull
protected abstract List<T> getChanges();
protected abstract List<? extends T> getChanges();
@NotNull
protected abstract EditorEx getEditor();
@@ -35,7 +35,7 @@ public abstract class PrevNextDifferenceIterableBase<T> implements PrevNextDiffe
@Override
public boolean canGoNext() {
List<T> changes = getChanges();
List<? extends T> changes = getChanges();
if (changes.isEmpty()) return false;
EditorEx editor = getEditor();
@@ -50,7 +50,7 @@ public abstract class PrevNextDifferenceIterableBase<T> implements PrevNextDiffe
@Override
public void goNext() {
List<T> changes = getChanges();
List<? extends T> changes = getChanges();
int line = getEditor().getCaretModel().getLogicalPosition().line;
T next = null;
@@ -68,7 +68,7 @@ public abstract class PrevNextDifferenceIterableBase<T> implements PrevNextDiffe
@Override
public boolean canGoPrev() {
List<T> changes = getChanges();
List<? extends T> changes = getChanges();
if (changes.isEmpty()) return false;
int line = getEditor().getCaretModel().getLogicalPosition().line;
@@ -83,7 +83,7 @@ public abstract class PrevNextDifferenceIterableBase<T> implements PrevNextDiffe
@Override
public void goPrev() {
List<T> changes = getChanges();
List<? extends T> changes = getChanges();
int line = getEditor().getCaretModel().getLogicalPosition().line;
T prev = null;
@@ -15,8 +15,8 @@
*/
package com.intellij.diff.tools.util;
import com.intellij.diff.util.IntPair;
import com.intellij.diff.util.Side;
import com.intellij.diff.util.ThreeSide;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.LogicalPosition;
@@ -24,7 +24,7 @@ import com.intellij.openapi.editor.ScrollingModel;
import com.intellij.openapi.editor.event.VisibleAreaEvent;
import com.intellij.openapi.editor.event.VisibleAreaListener;
import com.intellij.openapi.editor.ex.EditorEx;
import gnu.trove.TIntFunction;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.CalledInAwt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -44,37 +44,31 @@ public class SyncScrollSupport {
int transfer(@NotNull Side baseSide, int line);
}
public static class TwosideSyncScrollSupport {
@NotNull private final Editor myEditor1;
@NotNull private final Editor myEditor2;
public static class TwosideSyncScrollSupport extends SyncScrollSupportBase {
@NotNull private final List<? extends Editor> myEditors;
@NotNull private final SyncScrollable myScrollable;
@NotNull private final MyScrollHelper myHelper1;
@NotNull private final MyScrollHelper myHelper2;
private boolean myDisabled = false;
private boolean myDuringSyncScroll = false;
@NotNull private final ScrollHelper myHelper1;
@NotNull private final ScrollHelper myHelper2;
public TwosideSyncScrollSupport(@NotNull List<? extends Editor> editors, @NotNull SyncScrollable scrollable) {
this(Side.LEFT.select(editors), Side.RIGHT.select(editors), scrollable);
}
public TwosideSyncScrollSupport(@NotNull Editor editor1, @NotNull Editor editor2, @NotNull SyncScrollable scrollable) {
myEditor1 = editor1;
myEditor2 = editor2;
myEditors = editors;
myScrollable = scrollable;
myHelper1 = create(myEditor1, myEditor2, myScrollable, Side.LEFT);
myHelper2 = create(myEditor2, myEditor1, myScrollable, Side.RIGHT);
myHelper1 = create(Side.LEFT);
myHelper2 = create(Side.RIGHT);
}
public boolean isDuringSyncScroll() {
return myDuringSyncScroll;
@Override
@NotNull
protected List<? extends Editor> getEditors() {
return myEditors;
}
public void setDisabled(boolean value) {
if (myDisabled == value) LOG.warn(new Throwable("myDisabled == value: " + myDisabled + " - " + value));
myDisabled = value;
@Override
@NotNull
protected List<? extends ScrollHelper> getScrollHelpers() {
return ContainerUtil.list(myHelper1, myHelper2);
}
@NotNull
@@ -83,41 +77,176 @@ public class SyncScrollSupport {
}
public void visibleAreaChanged(VisibleAreaEvent e) {
if (!myScrollable.isSyncScrollEnabled() || myDuringSyncScroll || myDisabled) return;
if (!myScrollable.isSyncScrollEnabled() || isDuringSyncScroll()) return;
myDuringSyncScroll = true;
enterDisableScrollSection();
try {
if (e.getEditor() == myEditor1) {
if (e.getEditor() == Side.LEFT.select(myEditors)) {
myHelper1.visibleAreaChanged(e);
}
else if (e.getEditor() == myEditor2) {
else if (e.getEditor() == Side.RIGHT.select(myEditors)) {
myHelper2.visibleAreaChanged(e);
}
}
finally {
myDuringSyncScroll = false;
exitDisableScrollSection();
}
}
public void makeVisible(@NotNull Side masterSide,
int startLine1, int endLine1, int startLine2, int endLine2,
final boolean animate) {
Side slaveSide = masterSide.other();
doMakeVisible(masterSide.getIndex(), new int[]{startLine1, startLine2}, new int[]{endLine1, endLine2}, animate);
}
final IntPair offsets = getTargetOffsets(myEditor1, myEditor2, startLine1, endLine1, startLine2, endLine2);
@NotNull
private ScrollHelper create(@NotNull Side side) {
return ScrollHelper.create(myEditors, side.getIndex(), side.other().getIndex(), myScrollable, side);
}
}
final Editor masterEditor = masterSide.select(myEditor1, myEditor2);
final Editor slaveEditor = slaveSide.select(myEditor1, myEditor2);
public static class ThreesideSyncScrollSupport extends SyncScrollSupportBase {
@NotNull private final List<? extends Editor> myEditors;
@NotNull private final SyncScrollable myScrollable12;
@NotNull private final SyncScrollable myScrollable23;
final int masterOffset = masterSide.select(offsets.val1, offsets.val2);
final int slaveOffset = slaveSide.select(offsets.val1, offsets.val2);
@NotNull private final ScrollHelper myHelper12;
@NotNull private final ScrollHelper myHelper21;
@NotNull private final ScrollHelper myHelper23;
@NotNull private final ScrollHelper myHelper32;
int startOffset1 = myEditor1.getScrollingModel().getVisibleArea().y;
int startOffset2 = myEditor2.getScrollingModel().getVisibleArea().y;
final int masterStartOffset = masterSide.select(startOffset1, startOffset2);
public ThreesideSyncScrollSupport(@NotNull List<? extends Editor> editors,
@NotNull SyncScrollable scrollable12,
@NotNull SyncScrollable scrollable23) {
assert editors.size() == 3;
myHelper1.setAnchor(startOffset1, offsets.val1, startOffset2, offsets.val2);
myHelper2.setAnchor(startOffset2, offsets.val2, startOffset1, offsets.val1);
myEditors = editors;
myScrollable12 = scrollable12;
myScrollable23 = scrollable23;
myHelper12 = create(ThreeSide.LEFT, ThreeSide.BASE);
myHelper21 = create(ThreeSide.BASE, ThreeSide.LEFT);
myHelper23 = create(ThreeSide.BASE, ThreeSide.RIGHT);
myHelper32 = create(ThreeSide.RIGHT, ThreeSide.BASE);
}
@Override
@NotNull
protected List<? extends Editor> getEditors() {
return myEditors;
}
@Override
@NotNull
protected List<? extends ScrollHelper> getScrollHelpers() {
return ContainerUtil.list(myHelper12, myHelper21, myHelper23, myHelper32);
}
public void visibleAreaChanged(VisibleAreaEvent e) {
if (isDuringSyncScroll()) return;
enterDisableScrollSection();
try {
if (e.getEditor() == ThreeSide.LEFT.select(myEditors)) {
if (myScrollable12.isSyncScrollEnabled()) {
myHelper12.visibleAreaChanged(e);
if (myScrollable23.isSyncScrollEnabled()) myHelper23.visibleAreaChanged(e);
}
}
else if (e.getEditor() == ThreeSide.BASE.select(myEditors)) {
if (myScrollable12.isSyncScrollEnabled()) myHelper21.visibleAreaChanged(e);
if (myScrollable23.isSyncScrollEnabled()) myHelper23.visibleAreaChanged(e);
}
else if (e.getEditor() == ThreeSide.RIGHT.select(myEditors)) {
if (myScrollable23.isSyncScrollEnabled()) {
myHelper32.visibleAreaChanged(e);
if (myScrollable12.isSyncScrollEnabled()) myHelper21.visibleAreaChanged(e);
}
}
}
finally {
exitDisableScrollSection();
}
}
public void makeVisible(@NotNull ThreeSide masterSide, int[] startLines, int[] endLines, boolean animate) {
doMakeVisible(masterSide.getIndex(), startLines, endLines, animate);
}
@NotNull
private ScrollHelper create(@NotNull ThreeSide master, @NotNull ThreeSide slave) {
assert master != slave;
assert master == ThreeSide.BASE || slave == ThreeSide.BASE;
boolean leftSide = master == ThreeSide.LEFT || slave == ThreeSide.LEFT;
SyncScrollable scrollable = leftSide ? myScrollable12 : myScrollable23;
Side side;
if (leftSide) {
// LEFT - BASE -> LEFT
// BASE - LEFT -> RIGHT
side = Side.fromLeft(master == ThreeSide.LEFT);
}
else {
// BASE - RIGHT -> LEFT
// RIGHT - BASE -> RIGHT
side = Side.fromLeft(master == ThreeSide.BASE);
}
return ScrollHelper.create(myEditors, master.getIndex(), slave.getIndex(), scrollable, side);
}
}
//
// Impl
//
private abstract static class SyncScrollSupportBase {
private int myDuringSyncScrollDepth = 0;
public boolean isDuringSyncScroll() {
return myDuringSyncScrollDepth > 0;
}
public void enterDisableScrollSection() {
myDuringSyncScrollDepth++;
}
public void exitDisableScrollSection() {
myDuringSyncScrollDepth--;
assert myDuringSyncScrollDepth >= 0;
}
@NotNull
protected abstract List<? extends Editor> getEditors();
@NotNull
protected abstract List<? extends ScrollHelper> getScrollHelpers();
protected void doMakeVisible(final int masterIndex, int[] startLines, int[] endLines, final boolean animate) {
final List<? extends Editor> editors = getEditors();
final List<? extends ScrollHelper> helpers = getScrollHelpers();
final int count = editors.size();
assert startLines.length == count;
assert endLines.length == count;
final int[] offsets = getTargetOffsets(editors.toArray(new Editor[count]), startLines, endLines);
final int[] startOffsets = new int[count];
for (int i = 0; i < count; i++) {
startOffsets[i] = editors.get(i).getScrollingModel().getVisibleArea().y;
}
final Editor masterEditor = editors.get(masterIndex);
final int masterOffset = offsets[masterIndex];
final int masterStartOffset = startOffsets[masterIndex];
for (ScrollHelper helper : helpers) {
helper.setAnchor(startOffsets[helper.getMasterIndex()], offsets[helper.getMasterIndex()],
startOffsets[helper.getSlaveIndex()], offsets[helper.getSlaveIndex()]);
}
doScrollHorizontally(masterEditor, 0, false); // animation will be canceled by "scroll vertically" anyway
doScrollVertically(masterEditor, masterOffset, animate);
@@ -125,117 +254,64 @@ public class SyncScrollSupport {
masterEditor.getScrollingModel().runActionOnScrollingFinished(new Runnable() {
@Override
public void run() {
myHelper1.removeAnchor();
myHelper2.removeAnchor();
for (ScrollHelper helper : helpers) {
helper.removeAnchor();
}
int masterFinalOffset = masterEditor.getScrollingModel().getVisibleArea().y;
int slaveFinalOffset = slaveEditor.getScrollingModel().getVisibleArea().y;
if (slaveFinalOffset != slaveOffset) {
myDuringSyncScroll = true;
boolean animateSlaves = animate && masterFinalOffset == masterStartOffset;
for (int i = 0; i < count; i++) {
if (i == masterIndex) continue;
Editor editor = editors.get(i);
doScrollVertically(slaveEditor, slaveOffset, animate && masterFinalOffset == masterStartOffset);
int finalOffset = editor.getScrollingModel().getVisibleArea().y;
if (finalOffset != offsets[i]) {
enterDisableScrollSection();
slaveEditor.getScrollingModel().runActionOnScrollingFinished(new Runnable() {
@Override
public void run() {
myDuringSyncScroll = false;
}
});
doScrollVertically(editor, offsets[i], animateSlaves);
editor.getScrollingModel().runActionOnScrollingFinished(new Runnable() {
@Override
public void run() {
exitDisableScrollSection();
}
});
}
}
}
});
}
}
public static class ThreesideSyncScrollSupport {
private static abstract class ScrollHelper implements VisibleAreaListener {
@NotNull private final List<? extends Editor> myEditors;
@NotNull private final SyncScrollable myScrollable1;
@NotNull private final SyncScrollable myScrollable2;
@NotNull private final MyScrollHelper myHelper11;
@NotNull private final MyScrollHelper myHelper12;
@NotNull private final MyScrollHelper myHelper21;
@NotNull private final MyScrollHelper myHelper22;
private boolean myDisabled = false;
private boolean myDuringSyncScroll = false;
public ThreesideSyncScrollSupport(@NotNull List<? extends Editor> editors,
@NotNull SyncScrollable scrollable1,
@NotNull SyncScrollable scrollable2) {
assert editors.size() == 3;
myEditors = editors;
myScrollable1 = scrollable1;
myScrollable2 = scrollable2;
myHelper11 = create(editors.get(0), editors.get(1), myScrollable1, Side.LEFT);
myHelper12 = create(editors.get(1), editors.get(0), myScrollable1, Side.RIGHT);
myHelper21 = create(editors.get(1), editors.get(2), myScrollable2, Side.LEFT);
myHelper22 = create(editors.get(2), editors.get(1), myScrollable2, Side.RIGHT);
}
public void setDisabled(boolean value) {
if (myDisabled == value) LOG.warn(new Throwable("myDisabled == value: " + myDisabled + " - " + value));
myDisabled = value;
}
public void visibleAreaChanged(VisibleAreaEvent e) {
if (myDuringSyncScroll || myDisabled) return;
myDuringSyncScroll = true;
try {
if (e.getEditor() == myEditors.get(0)) {
if (myScrollable1.isSyncScrollEnabled()) myHelper11.visibleAreaChanged(e);
if (myScrollable1.isSyncScrollEnabled() && myScrollable2.isSyncScrollEnabled()) myHelper21.visibleAreaChanged(e);
}
else if (e.getEditor() == myEditors.get(1)) {
if (myScrollable1.isSyncScrollEnabled()) myHelper12.visibleAreaChanged(e);
if (myScrollable2.isSyncScrollEnabled()) myHelper21.visibleAreaChanged(e);
}
else if (e.getEditor() == myEditors.get(2)) {
if (myScrollable2.isSyncScrollEnabled()) myHelper22.visibleAreaChanged(e);
if (myScrollable2.isSyncScrollEnabled() && myScrollable1.isSyncScrollEnabled()) myHelper12.visibleAreaChanged(e);
}
}
finally {
myDuringSyncScroll = false;
}
}
}
@NotNull
private static MyScrollHelper create(@NotNull Editor master,
@NotNull Editor slave,
@NotNull final SyncScrollable scrollable,
@NotNull final Side side) {
return new MyScrollHelper(master, slave, new TIntFunction() {
@Override
public int execute(int value) {
return scrollable.transfer(side, value);
}
});
}
//
// Impl
//
private static class MyScrollHelper implements VisibleAreaListener {
@NotNull private final Editor myMaster;
@NotNull private final Editor mySlave;
@NotNull private final TIntFunction myConvertor;
private final int myMasterIndex;
private final int mySlaveIndex;
@Nullable private Anchor myAnchor;
public MyScrollHelper(@NotNull Editor master, @NotNull Editor slave, @NotNull TIntFunction convertor) {
myMaster = master;
mySlave = slave;
myConvertor = convertor;
public ScrollHelper(@NotNull List<? extends Editor> editors, int masterIndex, int slaveIndex) {
myEditors = editors;
myMasterIndex = masterIndex;
mySlaveIndex = slaveIndex;
}
@NotNull
public static ScrollHelper create(@NotNull List<? extends Editor> editors,
int masterIndex,
int slaveIndex,
@NotNull final SyncScrollable scrollable,
@NotNull final Side side) {
return new ScrollHelper(editors, masterIndex, slaveIndex) {
@Override
protected int convertLine(int value) {
return scrollable.transfer(side, value);
}
};
}
protected abstract int convertLine(int value);
public void setAnchor(int masterStartOffset, int masterEndOffset, int slaveStartOffset, int slaveEndOffset) {
myAnchor = new Anchor(masterStartOffset, masterEndOffset, slaveStartOffset, slaveEndOffset);
}
@@ -254,20 +330,38 @@ public class SyncScrollSupport {
if (newRectangle.y != oldRectangle.y) syncVerticalScroll(false);
}
private void syncVerticalScroll(boolean animated) {
if (myMaster.getDocument().getTextLength() == 0) return;
public int getMasterIndex() {
return myMasterIndex;
}
Rectangle viewRect = myMaster.getScrollingModel().getVisibleArea();
public int getSlaveIndex() {
return mySlaveIndex;
}
@NotNull
public Editor getMaster() {
return myEditors.get(myMasterIndex);
}
@NotNull
public Editor getSlave() {
return myEditors.get(mySlaveIndex);
}
private void syncVerticalScroll(boolean animated) {
if (getMaster().getDocument().getTextLength() == 0) return;
Rectangle viewRect = getMaster().getScrollingModel().getVisibleArea();
int middleY = viewRect.height / 3;
int offset;
if (myAnchor == null) {
LogicalPosition masterPos = myMaster.xyToLogicalPosition(new Point(viewRect.x, viewRect.y + middleY));
LogicalPosition masterPos = getMaster().xyToLogicalPosition(new Point(viewRect.x, viewRect.y + middleY));
int masterCenterLine = masterPos.line;
int convertedCenterLine = myConvertor.execute(masterCenterLine);
int convertedCenterLine = convertLine(masterCenterLine);
Point point = mySlave.logicalPositionToXY(new LogicalPosition(convertedCenterLine, masterPos.column));
int correction = (viewRect.y + middleY) % myMaster.getLineHeight();
Point point = getSlave().logicalPositionToXY(new LogicalPosition(convertedCenterLine, masterPos.column));
int correction = (viewRect.y + middleY) % getMaster().getLineHeight();
offset = point.y - middleY + correction;
}
else {
@@ -277,13 +371,13 @@ public class SyncScrollSupport {
offset = myAnchor.slaveStartOffset + (int)((myAnchor.slaveEndOffset - myAnchor.slaveStartOffset) * progress);
}
int deltaHeaderOffset = getHeaderOffset(mySlave) - getHeaderOffset(myMaster);
doScrollVertically(mySlave, offset + deltaHeaderOffset, animated);
int deltaHeaderOffset = getHeaderOffset(getSlave()) - getHeaderOffset(getMaster());
doScrollVertically(getSlave(), offset + deltaHeaderOffset, animated);
}
private void syncHorizontalScroll(boolean animated) {
int offset = myMaster.getScrollingModel().getVisibleArea().x;
doScrollHorizontally(mySlave, offset, animated);
int offset = getMaster().getScrollingModel().getVisibleArea().x;
doScrollHorizontally(getSlave(), offset, animated);
}
}
@@ -307,56 +401,83 @@ public class SyncScrollSupport {
}
@NotNull
private static IntPair getTargetOffsets(@NotNull Editor editor1, @NotNull Editor editor2,
int startLine1, int endLine1, int startLine2, int endLine2) {
int topOffset1 = editor1.logicalPositionToXY(new LogicalPosition(startLine1, 0)).y;
int bottomOffset1 = editor1.logicalPositionToXY(new LogicalPosition(endLine1 + 1, 0)).y;
int topOffset2 = editor2.logicalPositionToXY(new LogicalPosition(startLine2, 0)).y;
int bottomOffset2 = editor2.logicalPositionToXY(new LogicalPosition(endLine2 + 1, 0)).y;
private static int[] getTargetOffsets(@NotNull Editor editor1, @NotNull Editor editor2,
int startLine1, int endLine1, int startLine2, int endLine2) {
return getTargetOffsets(new Editor[]{editor1, editor2},
new int[]{startLine1, startLine2},
new int[]{endLine1, endLine2});
}
int rangeHeight1 = bottomOffset1 - topOffset1;
int rangeHeight2 = bottomOffset2 - topOffset2;
@NotNull
private static int[] getTargetOffsets(@NotNull Editor[] editors, int[] startLines, int[] endLines) {
int count = editors.length;
assert startLines.length == count;
assert endLines.length == count;
int gapLines1 = 2 * editor1.getLineHeight();
int gapLines2 = 2 * editor2.getLineHeight();
int[] topOffsets = new int[count];
int[] bottomOffsets = new int[count];
int[] rangeHeights = new int[count];
int[] gapLines = new int[count];
int[] editorHeights = new int[count];
int[] maximumOffsets = new int[count];
int[] topShifts = new int[count];
int editorHeight1 = editor1.getScrollingModel().getVisibleArea().height;
int editorHeight2 = editor2.getScrollingModel().getVisibleArea().height;
for (int i = 0; i < count; i++) {
topOffsets[i] = editors[i].logicalPositionToXY(new LogicalPosition(startLines[i], 0)).y;
bottomOffsets[i] = editors[i].logicalPositionToXY(new LogicalPosition(endLines[i] + 1, 0)).y;
rangeHeights[i] = bottomOffsets[i] - topOffsets[i];
int maximumOffset1 = ((EditorEx)editor1).getScrollPane().getVerticalScrollBar().getMaximum() - editorHeight1;
int maximumOffset2 = ((EditorEx)editor2).getScrollPane().getVerticalScrollBar().getMaximum() - editorHeight2;
gapLines[i] = 2 * editors[i].getLineHeight();
editorHeights[i] = editors[i].getScrollingModel().getVisibleArea().height;
// 'shift' here - distance between editor's top and first line of range
maximumOffsets[i] = ((EditorEx)editors[i]).getScrollPane().getVerticalScrollBar().getMaximum() - editorHeights[i];
// make whole range visible. If possible, locate it at 'center' (1/3 of height)
// If can't show whole range - show as much as we can
boolean canShow1 = 2 * gapLines1 + rangeHeight1 <= editorHeight1;
boolean canShow2 = 2 * gapLines2 + rangeHeight2 <= editorHeight2;
int topShift1 = canShow1 ? Math.min(editorHeight1 - gapLines1 - rangeHeight1, editorHeight1 / 3) : gapLines1;
int topShift2 = canShow2 ? Math.min(editorHeight2 - gapLines2 - rangeHeight2, editorHeight2 / 3) : gapLines2;
// 'shift' here - distance between editor's top and first line of range
int topShift = Math.min(topShift1, topShift2);
// make whole range visible. If possible, locate it at 'center' (1/3 of height)
// If can't show whole range - show as much as we can
boolean canShow = 2 * gapLines[i] + rangeHeights[i] <= editorHeights[i];
topShifts[i] = canShow ? Math.min(editorHeights[i] - gapLines[i] - rangeHeights[i], editorHeights[i] / 3) : gapLines[i];
}
int topShift = min(topShifts);
// check if we're at the top of file
topShift = Math.min(topShift, Math.min(topOffset1, topOffset2));
topShift = Math.min(topShift, min(topOffsets));
int offset1 = topOffset1 - topShift;
int offset2 = topOffset2 - topShift;
if (maximumOffset1 > offset1 && maximumOffset2 > offset2) return new IntPair(offset1, offset2);
int[] offsets = new int[count];
boolean haveEnoughSpace = true;
for (int i = 0; i < count; i++) {
offsets[i] = topOffsets[i] - topShift;
haveEnoughSpace &= maximumOffsets[i] > offsets[i];
}
if (haveEnoughSpace) return offsets;
// One of the ranges is at end of file - we can't scroll where we want to.
topShift = Math.max(topOffset1 - maximumOffset1, topOffset2 - maximumOffset2);
topShift = 0;
for (int i = 0; i < count; i++) {
topShift = Math.max(topOffsets[i] - maximumOffsets[i], topShift);
}
// Try to show as much of range as we can (even if it breaks alignment)
offset1 = topOffset1 - topShift + Math.max(topShift + rangeHeight1 + gapLines1 - editorHeight1, 0);
offset2 = topOffset2 - topShift + Math.max(topShift + rangeHeight2 + gapLines2 - editorHeight2, 0);
for (int i = 0; i < count; i++) {
// Try to show as much of range as we can (even if it breaks alignment)
offsets[i] = topOffsets[i] - topShift + Math.max(topShift + rangeHeights[i] + gapLines[i] - editorHeights[i], 0);
// always show top of the range
offset1 = Math.min(offset1, topOffset1 - gapLines1);
offset2 = Math.min(offset2, topOffset2 - gapLines2);
// always show top of the range
offsets[i] = Math.min(offsets[i], topOffsets[i] - gapLines[i]);
}
return new IntPair(offset1, offset2);
return offsets;
}
private static int min(int[] values) {
int min = Integer.MAX_VALUE;
for (int value : values) {
if (value < min) min = value;
}
return min;
}
private static class Anchor {
@@ -18,21 +18,29 @@ package com.intellij.diff.tools.util.base;
import com.intellij.diff.DiffContext;
import com.intellij.openapi.actionSystem.DataProvider;
import com.intellij.openapi.project.Project;
import com.intellij.ui.components.panels.Wrapper;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
public abstract class DiffPanelBase extends JPanel implements DataProvider {
@Nullable protected final Project myProject;
@NotNull private final DataProvider myDataProvider;
@NotNull protected final DiffContext myContext;
@NotNull private final List<JComponent> myPersistentNotifications = new ArrayList<JComponent>();
@NotNull protected final JPanel myContentPanel;
@NotNull protected final JPanel myNotificationsPanel;
@NotNull private final Wrapper myNorthPanel;
@NotNull private final Wrapper mySouthPanel;
@NotNull protected final CardLayout myCardLayout;
@NotNull protected String myCurrentCard;
@@ -51,23 +59,20 @@ public abstract class DiffPanelBase extends JPanel implements DataProvider {
myNotificationsPanel = new JPanel();
myNotificationsPanel.setLayout(new BoxLayout(myNotificationsPanel, BoxLayout.Y_AXIS));
myNorthPanel = new Wrapper();
mySouthPanel = new Wrapper();
add(myContentPanel, BorderLayout.CENTER);
JComponent topPanel = createTopPanel();
if (topPanel != null) add(topPanel, BorderLayout.NORTH);
JComponent bottomPanel = createBottomPanel();
if (bottomPanel != null) add(bottomPanel, BorderLayout.SOUTH);
add(myNorthPanel, BorderLayout.NORTH);
add(mySouthPanel, BorderLayout.SOUTH);
}
@Nullable
public JComponent createTopPanel() {
return null;
public void setTopPanel(@Nullable JComponent component) {
myNorthPanel.setContent(component);
}
@Nullable
public JComponent createBottomPanel() {
return null;
public void setBottomPanel(@Nullable JComponent component) {
mySouthPanel.setContent(component);
}
protected void setCurrentCard(@NotNull String card) {
@@ -94,8 +99,25 @@ public abstract class DiffPanelBase extends JPanel implements DataProvider {
// Notifications
//
public void setPersistentNotifications(@NotNull List<JComponent> components) {
for (JComponent notification : myPersistentNotifications) {
myNotificationsPanel.remove(notification);
}
myPersistentNotifications.clear();
myPersistentNotifications.addAll(components);
for (JComponent notification : myPersistentNotifications) {
myNotificationsPanel.add(notification);
}
myNotificationsPanel.revalidate();
}
public void resetNotifications() {
myNotificationsPanel.removeAll();
for (JComponent notification : myPersistentNotifications) {
myNotificationsPanel.add(notification);
}
myNotificationsPanel.revalidate();
}
@@ -125,7 +125,7 @@ public abstract class DiffViewerBase implements DiffViewer, DataProvider {
}
@CalledInAwt
public final void rediff(boolean trySync) {
public void rediff(boolean trySync) {
if (isDisposed()) return;
abortRediff();
@@ -57,6 +57,12 @@ public abstract class OnesideDiffViewer<T extends EditorHolder> extends Listener
myPanel = new SimpleDiffPanel(myContentPanel, this, context);
}
@Override
protected void onInit() {
super.onInit();
myPanel.setPersistentNotifications(DiffUtil.getCustomNotifications(myContext, myRequest));
}
@Override
@CalledInAwt
protected void onDispose() {
@@ -66,6 +66,12 @@ public abstract class ThreesideDiffViewer<T extends EditorHolder> extends Listen
myPanel = new SimpleDiffPanel(myContentPanel, this, context);
}
@Override
protected void onInit() {
super.onInit();
myPanel.setPersistentNotifications(DiffUtil.getCustomNotifications(myContext, myRequest));
}
@Override
@CalledInAwt
protected void onDispose() {

Some files were not shown because too many files have changed in this diff Show More